距离上次更新本文已经过去了 733 天,文章部分内容可能已经过时,请注意甄别

从今天开始,正式步入 C++ 学习的大门啦!

C++ 的博客主要是我个人学习的学习笔记,刚开始会记一些比较零碎的小知识点,可能没有之前 C 语言的博客那么系统化😂

笔记中可能有错误的地方,欢迎大家指出!

感谢你关注慕雪,欢迎来我的寒舍坐坐❄慕雪的寒舍

[TOC]

1.C++ 简介

你可以通过菜鸟教程网来学习 C++👉传送门

C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。

C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点。

C++ 是由 Bjarne Stroustrup 于 1979 年在新泽西州美利山贝尔实验室开始设计开发的。C++ 进一步扩充和完善了 C 语言,最初命名为带类的 C,后来在 1983 年更名为 C++。

C++ 是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序。


2.C++ 关键字

C 语言有 32 个关键字,C++ 在它们的基础上进行了扩容,共有 63 个关键字

asmelsenewthisautoenum
operatorthrowboolexplicitprivatetrue
breakexportprotectedtrycaseextern
publictypedefcatchfalseregistertypeid
charfloatreinterpret_casttypenameclassfor
returnunionconstfriendshortunsigned
const_castgotosignedusingcontinueif
defaultinlinesizeofvirtualstaticvoid
dolongstructwchar_tdeleteint
static_castvolatiledoublemutableswitchwhile
dynamic_castnamespacetemplate

如果你想查看它们的完整介绍,可以参考这篇教程👉传送门

这里只做一个基本的认识,后续会一一学习这些关键字


3. 安装你的 C++ 环境

  • 如果是 windows 系统,建议使用微软的 VisualStudio,不要用 dev 这种老式编译器!
  • 如果是 mac 系统,可以使用 Xcode(我没有 mac 不知道具体情况)
  • 如果是 linux 系统,需要安装 G++ 编译器

利用我的树莓派,给大家演示一下 linux 环境安装 G++ 编译器

下面这个语句是显示 g++ 编译器的版本,如果你弹出了和我一样的页面,说明你的 linux 系统里面已经有这个编译器了

plaintext
1
g++ -v

image-20220429105312608

如果没有,我们可以使用下面这个语句来整一个 g++

plaintext
1
sudo apt-get install g++

你也可以用这个语句来验证你的 G++ 是否已经安装

image-20220429105527074

下载完成后,就可以愉快的使用你的 G++ 编译器了

如果你的下载很慢,可能要考虑换源(即更换一个软件下载源)

树莓派实验室详细记录了国内比较好用的源,也有换源教程👉传送门


4.C++ 的输入输出

4.1cout 和 cin 样例

老规矩,向这个世界问个好吧!

cpp
1
2
3
4
5
6
7
#include <iostream>
using namespace std;
int main()
{
cout << "Hello, world!" << endl;
return 0;
}

image-20220429110109339

这里出现的 endl 是 “end of line” 的缩写,结束一行,相当于 C 语言中的 \n

因为 C++ 的编译器是完成兼容 C 语言的,所以你可以用 "\n" 来替换 endl

image-20220429110631349


cout 是流提取操作符,对应的,也有流插入操作符 cin。你可以理解为它们就是 C++ 中的 printf 和 scanf

注意这里的箭头方向,不要弄错了

cpp
1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;
int main()
{
cout << "Hello, world!" << endl;
int a;
cin >> a;
cout << a << endl;
return 0;
}

image-20220429110421373

4.2 头文件

使用这两个 “库函数” 时,必须要要包含 iostream 头文件以及 std 标准命名空间

cplusplus.com 上搜索,可以看到 iostream 是标准输入输出流的库

image-20220429111025314

4.3 和 C 语言的不同

使用 C++ 来输入输出更加方便,因为我们不需要增加格式控制符%d %c %f 这种)

因为编译器会自己判断它们的类型

image-20220429111339782

同样的,我们可以使用连续的 >> 来输入多个数据

image-20220429112824978

4.4 浮点数默认保留 5 位小数

通过两个程序的测试,我们可以看到 C++ 中默认保留小数是 5 位,而 C 语言中是 6 位

image-20220429112851922

image-20220429112802912

那么问题来了,如果我想控制输出格式,让它输出小数点后两位可以怎么做呢?

很简单,直接调用 printf%.2f 不就完事了?

C++ 中也有自己的格式控制方式,但是那个比 printf 更复杂

image-20220429113049750

而且在 C++ 中打印低于 5 位的浮点数,并不会像 C 语言一样后补 0

image-20220429120709773

注意注意!如果在 C++ 程序中使用 printf,就不能在 printf 里面写 endl 啦!


5. 命名空间

在输入输出函数中,有这么一个语句

cpp
1
using namespace std;

前面已经提到过,std 是” 标准命名空间”

那什么是命名空间呢?


5.1 简单介绍

在 main 函数中,如果出现了两个相同名字的参数,编译器会报错

image-20220429121458107

假设一个班级上有两个同名的同学,为了区分他们俩人,我们会使用一些附加的条件来称呼二者。比如性别、家庭住址、甚至是成绩

C++ 中的命名空间就是用来干这个事的

项目合作的时候,命名空间很有用,可以有效避免两个人写的程序中出现同样名字的变量而导致冲突的情况

5.2 设置一个命名空间

比如现在我们设置一个命名空间,把 int a 放入其中

cpp
1
2
3
namespace muxue {
int a = 1;
}

可以看到,这个时候编译程序就不会报错了

image-20220429121810966

但是程序打印的是 main 函数中的 double 类型 a,如果我们想用 muxue 中的 int a 要怎么操作呢?

只需要在变量名之前用:: 来指定命名空间就可以了

image-20220429122021654

当然,我们也可以使用 using namespace muxue; 来直接展开这个命名空间中的所有东西

但是这样不太好!后面会提到

如果你定义了两个相同名字的命名空间,在编译程序的时候,编译器会合并他们


5.2.1 嵌套定义命名空间

定义命名空间就和 C 语言的结构体很类似,是可以嵌套定义另外一个命名空间的

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//命名空间
namespace muxue {
int a = 1;
//嵌套定义命名空间
namespace happy {
int b = 2;
}
}

int main() {
double a = 3.14;
cout << a << endl;
cout << muxue::a <<endl;
cout << muxue::happy::b << endl;
return 0;
}

image-20220429125952967

直接展开两个命名空间后,就不需要这样写输出函数了

image-20220429130715269


我们还可以在已有命名空间中嵌套定义一个命名空间,里面包含另外一个 a

cpp
1
2
3
4
5
6
7
namespace muxue {
int a = 1;
//嵌套定义命名空间
namespace happy {
int a = 2;
}
}

这时候如果使用 using namespace muxue;,在 main 函数中会默认打印外层命名空间中的 a

image-20220429130350841

但如果你比较喜欢作死,把内层命名空间也展开了,就会出现冲突

cpp
1
2
using namespace muxue;
using namespace muxue::happy;

image-20220429130409776


除了嵌套定义中这种冲突的情况,我们直接展开命名空间时,还会遇到其他冲突

5.3 全部展开可能遇到的问题

5.3.1 和库函数冲突

比如下面这个情况,我们在 muxue 里面定义了一个 int 类型叫做 rand,并在之后直接 using namespace 完整展开了这个命名空间

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <stdlib.h>
using namespace std;
namespace muxue {
int a = 1;
int rand = 0;
}
using namespace muxue;
int main(){
a += 1;
cout << a << endl;

rand = 10;
printf("%d\n", rand);
return 0;
}

然后你就会发现,程序 boom 了!

image-20220429124426773

这是因为在 <stdlib.h> 头文件中有一个生成随机数的 rand 函数,现在程序不知道你是要用命名空间中的变量 rand,还是想调用这个库函数了!

cpp
1
2
muxue::rand = 10;
printf("%d\n", muxue::rand);

这时候我们就要选择单独调用命名空间中的 rand 变量,而不是直接使用变量名

image-20220429124804989

同时,顶部的 using namesapce 也需要做相应修改

cpp
1
2
//using namespace muxue;
using muxue::a;

5.3.2 和其他命名空间冲突

cpp
1
2
3
4
5
6
7
8
9
10
namespace muxue {
int a = 1;
int rand = 0;
}
namespace haha {
int a = 3;
}

using namespace muxue;
using namespace haha;

假设两个命名空间中都有 a 变量,这时候程序也不知道你要调用哪一个 a 了,同样会报错

image-20220429125242919

这就好比班级里有俩个叫李华的人,本来一个是男的,一个是女的,咱们可以用性别区分

直接完整展开了命名空间,就好比让这两个李华都变成男生

这不 BBQ 了吗?还怎么用性别判断谁是谁?

所以在很多时候,尽量使用:: 单独展开某一个变量,而不是展开整个命名空间

这种冲突的情况,被称为命名空间污染


5.4 避免命名空间污染

在菜鸟教程上,可以看到有大佬做了一个这样的笔记

image-20220429122114941

是的,有的时候我们自定义的变量类型名会和 std 标准命名空间冲突,这时候直接在最前面 using namespace std; 就会出 5.3 中提到的问题

可 cout 和 cin 这两个家伙天天要用,总不能每一次使用的时候,都写一个 std:: 吧?那样也太麻烦了!

别急,我们也可以单独展开这几个小家伙!

cpp
1
2
3
using std::cin;
using std::cout;
using std::endl;

image-20220429122624247

其他一些问题在之前 5.25.3 都已经提到过了,这里就不再说啦!

个人理解,即便 C++ 有命名空间这个好东西,在定义变量的时候最好还是保持一个良好的编程习惯,不用关键字和库函数名作为变量名

5.5 :: 指定全局作用域

当我们默认使用:: 而不在前面添加命名空间的名字的时候,默认会指定到全局的命名空间。即便函数被另外一个命名空间所包裹,也是会调用全局作用域下的函数

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//全局
void _print()
{
    cout << "global print" << endl;
}
//命名空间
namespace muxue
{
    void _print()
    {
        cout << "namespace muxue print" << endl;
    }

    void testNamespace()
    {
        ::_print();//调用全局作用域的函数
    }
}

int main()
{
    muxue::_print();//调用命名空间内的print
    muxue::testNamespace();

    return 0;
}

打印的结果如下

image-20220920211415931


6. 缺省参数

缺省参数是函数在定义的时候,给指定的参数一个默认值,如果没有指定实参,就会使用这个默认值。比如:

cpp
1
2
3
4
5
6
7
8
9
10
//缺省参数
void Add(int a = 10,int b = 20) {
cout << a + b << endl;
}
int main() {
int n = 1, m = 2;
Add(n, m);//指定了实参
Add();//没有指定
return 0;
}

image-20220429132302242

除了这两种方式外,我们还可以只给该函数传一个实参

image-20220429132406777

但是像下面这样,想给函数中的 b 传一个参数是不可以的

image-20220429132447278

6.1 全缺省和半缺省

  • 像上面这种,函数中的所有形参都设定了缺省值的,叫做全缺省
  • 函数中只有一部分设置了缺省值的,叫做半缺省

注意:设置缺省参数的时候,必须从右往左设置,不能出现中间空一个的情况


6.1.1 全缺省

比如这么一个有 3 个参数的函数,我们全部设置了缺省值,就是全缺省

cpp
1
2
3
4
5
void Print(int a = 10, int b = 20,int c=30) {
cout << a << endl;
cout << b << endl;
cout << c << endl;
}

我们可以直接传 3 个值,进行对应的打印,也可以只传两个、一个或者不传形参

image-20220429133330394

这种中间间隔一个传参是不允许的

image-20220429133120425


6.1.2 半缺省

半缺省是缺省了一部分的函数

cpp
1
2
3
4
5
void Print2(int a, int b = 20, int c = 30) {
cout << a << endl;
cout << b << endl;
cout << c << endl;
}

在调用的时候,我们就不能不传参数了

image-20220429133649270

至少要传一个参数给我们的 a

image-20220429133702436

半缺省的函数可以是如下形式

cpp
1
2
void Print2(int a, int b = 20, int c = 30);
void Print2(int a, int b, int c = 30);

但不能是这样

cpp
1
void Print2(int a=10, int b, int c = 30);

因为这种情况会出现歧义,编译器不知道你的参数是给 b 还是给 a


6.2 缺省参数的应用

比如在创建一个顺序表的时候,我们可以利用缺省参数来设置默认长度

这样就避免了单一默认长度导致的多次扩容(realloc 扩容是有性能浪费的!)

image-20220429134452258

比如我知道我这次需要一个很长的顺序表,直接改默认长度不太合适。使用这种方法传一个新的长度值更加方便我们的动态管理!

缺省参数是只有 C++ 才支持的函数定义方式,这也是 C++ 比 C 语言更优的体现

6.3 多文件编程中的缺省

使用多文件编程的时候,缺省值只需要在头文件中给出即可

image-20220429135027368

不需要在函数定义的源文件中给出,编译器会报错

假设函数的声明和定义中设定的缺省值不同,编译器就不知道要用哪一个缺省值

image-20220429135208753

调用函数,可以看到函数正确分配了不同的初始值

image-20220429135854460


6.3.1 错误情况

但如果我们在头文件中不包含缺省,函数就会报错,不支持我们只传 1 个参数

cpp
1
void InitArr(Qa* q, int n);

image-20220429135334165

只要你的头文件中声明没有缺省, 在函数定义的源文件中加入缺省也是没用的!

因为编译器展开头文件是在 main 函数中之上,在查找函数的时候,只会向上查找声明。所以函数的声明是以头文件为准

image-20220429135534869


结语

第一篇 C++ 的笔记就写这么多(其实已经有几千字了)

下一篇笔记是函数重载,可以让你感受到 C++ 的更多花样!

不会有人把博客看完了还不点赞吧?

QQ图片20220419102702