目录
1、命名空间
一个大型项目往往都是由多名程序员共同完成的,因此会用到大量的变量和函数以及C++里面的类,所以难免会出现命名相同的情况。就算当前程序在你的机器上运行是正常的,当合并时就会出现命名冲突。
举个例子:
namespace A{
int a = 10;//A定义的变量a
}
namespace B{
int a = 6;//B定义的变量a
}
如上如果合并的时候,a变量会被重定义,所以C++引入了命名空间(namespace)的概念。
语法:namespace name{ //name任意名字都可以,只要不跟其他命名空间名冲突
//变量、函数、类
}
命名空间的使用
Ⅰ 使用命名空间名加作用域限定符,来指明要使用的命名空间
namespace A //A是命名空间名
{
int a = 1;
int b = 2;
int Add(int a, int b)
{
return a + b;
}
}
int main()
{
printf("%d\n", A::a); //1
printf("%d\n", A::b); //2
int sum = A::Add(A::a, A::b);
printf("%d\n", sum); //3
return 0;
}
Ⅱ 使用using关键字声明要使用的命名空间成员
using A::a;//引入A空间里的变量a
using A::b;
using A::Add;//引入A空间里的函数Add
int main()
{
printf("%d\n", a); //1
printf("%d\n", b); //2
int sum = Add(a, b);
printf("%d\n", sum); //3
return 0;
}
Ⅲ 使用using namespace 命名空间名称引入
using namespace A;
int main()
{
printf("%d\n", a); //1
printf("%d\n", b); //2
int sum = Add(a, b);
printf("%d\n", sum); //3
system("pause");
return 0;
}
注意:当main函数内有重名的变量时,需要特别声明该变量属于哪里
using namespace A;
int main()
{
int a = 6;
printf("%d\n", A::a);//1
printf("%d\n", a); //6
printf("%d\n", b); //2
int sum1 = Add(A::a, b);//必须指明a属于那里,否则采取就近原则使用main函数里的a
int sum2 = Add(a, b);//使用的是main函数里的a
printf("%d\n", sum1);//3
printf("%d\n", sum2);//8
system("pause");
return 0;
}
当有同名的Add函数时,同样需要指明使用的是哪里的Add函数
namespace A
{
int a = 1;
int b = 2;
int Add(int a, int b)
{
return a + b;
}
}
using namespace A;
int Add(int left, int right)
{
return left * 10 + right * 10;
}
int main()
{
int a = 6;
printf("%d\n", A::a); //1
printf("%d\n", b); //2
int sum1 = ::Add(A::a, b);//使用的是全局域的Add
int sum2 = A::Add(A::a, b);//使用的是命名空间A里的Add函数
printf("%d\n", sum1); //30
printf("%d\n", sum2); //3
return 0;
}
2、C++输入输出
在C++中输入输出使用的是cout和cin,相对于C语言的printf和scanf使用起来方便一些。
具体使用示例:
#include<iostream>
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
1、使用使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。
2、使用cin和cout输入输出数据时不用添加数据格式控制,比如:整形--%d,字符--%c,因为它会自动推导。
#include<iostream>
using namespace std;
int main()
{
int a = 1;
double b = 1.1;
char c;
cin >> c;
cout << a << endl; //1
cout << b << endl; //1.1
cout << c << endl; //你输入的字符
return 0;
}
3、缺省参数
缺省参数是声明或者定义函数时给函数的参数指定一个默认值。这样当你调用该函数时可以传值,也可以不传直接使用默认值。
具体用法:
#include<iostream>
using namespace std;
//int Add(int x = 10, int y = 20, int z = 30) //全缺省
int Add(int x, int y = 20, int z = 30) //半缺省
{
return x + y + z;
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
cout << Add(a, b, c) << endl;//6 //指定值
cout << Add() << endl; //全缺省可以不传值
//cout << Add() << endl;//当你是半缺省时,没给初始值的得传值,所以这种半缺省时会报错
cout << Add(10) << endl;//60 //半缺省,有几个没给初始值就传几个
return 0;
}
特别声明:
1、如果 x 给了缺省值,那么它右边的参数就必须给缺省值,半缺省参数必须从右往左依次来给默认值,不能间隔着给。
2、当你传了值时,优先使用传过来的值。
3、缺省参数在声明和定义中不能同时出现
// .h
void Func(int a = 10);
// .cpp
void Func(int a = 20)
{}
//上面的写法是错误的,编译器无法确定使用哪个缺省值
//下面是正确写法
// .h
void Func(int a = 10);
// .cpp
void Func(int a)
{}
4、缺省值必须是常量或者全局变量
5、C语言不支持缺省参数
4、函数重载
概念:函数重载就相当于一词多义,在C++同一作用域中,允许声明多个同名函数,他们执行的功能类似,这些同名函数的形参列表(参数个数或类型或顺序)必须不同。
具体实例:
#include<iostream>
using namespace std;
int Add(int x,int y)
{
return x + y;
}
double Add(int x, double y)
{
return x + y;
}
int Add(int x)
{
return x;
}
int main()
{
int a = 1;
int b = 2;
double c = 1.1;
cout << Add(a, b) << endl;//3
cout << Add(a, c) << endl;//2.1
cout << Add(a) << endl; //1
system("pause");
return 0;
}
注意:重载函数与函数返回值没有任何关系
函数名修饰规则
为什么C语言不支持函数重载而C++支持呢?
在C/C++中 ,程序要经历预处理、编译、汇编、链接才能生成可执行程序。
预处理阶段:展开头文件、替换宏、删除注释..,生成 .i 文件
编译:进行词法分析、语法分析、语义分析、代码优化,将代码转换成汇编代码,生成 .s 文件
汇编:将汇编代码转换成机器能读懂的二进制代码,生成 .o 文件
链接:将生成的多个 .o 文件链接起来,最后生成 .exe 可执行程序
1、假设这里所有Add函数声明和定义在 A.cpp 文件中,main函数在 test.cpp 中,在编译后链接前,当需要调用Add函数时,由于 test.o 中没有Add函数的定义,就需要去 A.o 中去找。
2、所以链接阶段就需要处理这个问题,当需要调用test.o调用Add函数,就需要知道Add函数的地址,所以就要到A.o文件中去找Add函数,然后链接起来。
3、这里就引入了函数名修饰规则的概念,由于windows下vs修饰规则过于复杂,所以在linux下实验。
上面gcc编译后, 函数名字没有发生任何改变。
可以看到每个Add函数的名字都不同。
windows下的函数名修饰规则可以自行百度了解一下。
结论:C语言同名函数无法区分所以不支持函数重载,C++通过函数修饰规则区分同名函数,最后修饰的函数名不同,所以支持函数重载。
extern"C"
在函数前加extern "C",意思是告诉编译器, 将该函数按照C语言规则来编译。
使用方法:
extern "C" int Add(int a, int b)
{
return a + b;
}
int main()
{
Add(1,2);
return 0;
}
因为C++支持函数重载,在编译器编译函数时会带上函数名加参数类型,如果加上extern "C",那么编译函数时就不会带上参数类型,只会用函数名。
总而言之:extern "C" 的作用就是实现C和C++之间代码的相互调用。
5、引用
引用概念
引用不同于指针,不是给重新定义一个变量,而是给已经存在的变量取一个别名,就相当于你家人给你取的小名或者同学给你取得绰号,所以说一个变量的引用可以有多个,引用的变量共用同一块内存空间。
举例:
int main()
{
int a = 10;
int& b = a;
int& c = a;//b、c就是a的引用
cout << b << " " << c << endl;//10、10
int& d = b;//d是b的引用,本质上代表a
d = 20;
cout << a << " " << d << endl;//20、20
cout << "a地址:" << &a << endl;
cout << "b地址:" << &b << endl;
cout << "c地址:" << &c << endl;
cout << "d地址:" << &d << endl;
return 0;
}
可以看到a、b、c、d共享同一块空间。
注意:
1、引用类型必须与引用实体的类型一致。
2、引用在定义时必须初始化
3、引用初始化后不可更改引用的实体
4、一个变量可以有多个引用
常引用
int main()
{
const int a = 10;
//a是const int类型,是常量,值不可修改
//而b是int类型,可修改值,属于权限的放大
//int& b = a;//err
const int& b = a;//这种才是正确的
int x = 10;
const int& y = x;//权限缩小
int& z = x;
z = 20;
//y = 20;//y是const类型,不可修改值,err
return 0;
}
引用的使用场景
1、引用做参数
void print(int& s)
{
s += 10;
cout << s << endl;//20
}
int main()
{
int a = 10;
print(a);
cout << a << endl;//20
return 0;
}
如上,当int& s改为int s时,我们知道形参是实参的一份临时拷贝,形参的改变不会改变实参,但是当你是引用传参时,s 就是 a 的引用,相当于就是 a 本身,所以 s 的改变也会影响 a,当 a 的空间占用很大时,引用传参的价值就体现的很明显,因为引用传参不会生成额外空间。
2、引用做返回值
int s;
int& print()
{
s += 10;
return s;
}
int main()
{
cout << print() << endl;//10
return 0;
}
当返回的变量出了函数作用域还没有销毁时,就可以使用引用传参做返回值;如果出了作用域变量销毁就只能传值返回,此时就会发生拷贝,将 s 的值拷贝给临时变量,再将临时变量的值拷贝给调用方。
结论:引用传参和引用做返回值能提高效率。
引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
上面的实验也看到引用和实体共享同一块空间。
其实在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
下面通过一段代码来对比一下指针和引用:
int main()
{
int a = 10;
int& b = a;
b = 15;
int* p = &a;
*p = 15;
return 0;
}
可以看到引用和指针的汇编代码是一模一样的。
那么引用和指针有什么不同呢?
1. 引用在定义时必须初始化,指针没有要求
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
3. 没有NULL引用,有NULL指针
4. 引用大小为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节)
5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全
6、内联函数
概念:
inline是C++关键字,在函数声明或定义中,函数返回类型前加上关键字inline,即可以把函数指定为内联函数。这样可以解决一些频繁调用的函数大量消耗栈空间(栈内存)的问题。关键字inline必须与函数定义放在一起才能使函数成为内联函数,仅仅将inline放在函数声明前面不起任何作用。inline是一种“用于实现”的关键字,而不是一种“用于声明”的关键字。
可以看到添加inline关键字后,main函数不会进入Add函数,而是变成两条汇编指令,我们知道调用函数需要建立栈帧,加了inline关键字后,就节省创建销毁栈帧的开销,提升效率。
如果需要查看inline运行效果,这里以vs2022为例:
特性
1、内联函数是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
2、inline对于编译器来说只是一种建议,具体会不会定义成内联函数,取决于编译器,小函数编译器在release中一般都会自动优化为内联函数。
3、内联函数不建议声明和定义分离编写,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会 找不到。
7、auto关键字
在C++11之前,auto关键字用来指明变量的存储类型,auto表示变量是自动存储的,所以在我们编写代码时一般不写,因为自动存储是编译器默认就有的规则。因此auto就没有了存在感。
在C++11之后,auto的可以说非常好用。此后的auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
使用方法:
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
cout << typeid(b).name() << endl;//int
cout << typeid(c).name() << endl;//char
return 0;
}
auto自动推导出了 b 和 c 的类型。
【注意】 使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为 变量实际的类型。
auto使用细节
1、auto和指针引用一起使用
int main()
{
int x = 10;
//a 和 b 指向的都是x的地址空间
//a 推导的是int*,b 推导的是int
auto a = &x;
auto* b = &x;
//要让 c 成为 x 的引用,必须加 & 符号
auto& c = x;
auto d = x;//这种只是单纯的将 x 的值赋给 d,两个地址不同
cout << typeid(a).name() << endl;//int*
cout << typeid(b).name() << endl;//int*
cout << typeid(c).name() << endl;//int
cout << typeid(d).name() << endl;//int
cout << "x的地址:" << &x << endl;
cout << "c的地址:" << &c << endl;
cout << "d的地址:" << &d << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
2. 在同一行定义多个变量
int main()
{
auto a = 1, b = 2;
//x、y类型不同,auto一次推到多个值时,这些值类型必须相同
//auto x = 10, y = 10.1;//err
return 0;
}
3、auto不能作为函数参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
//void Test(auto a)//err
void Test(int a)
{}
4、auto不能直接用来声明数组
int main()
{
int a[] = {1,2,3};
//auto b[] = {4,5,6};//err
return 0;
}
8、范围for循环
C++11新增语法
语法
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
//普通for循环打印
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout << arr[i] << " ";
}
cout << endl;
//范围for循环打印
//利用auto自动推导arr类型,也可以指定类型
//e 只是一个变量名,随便取
//for (auto e : arr)
for (int e : arr)
{
cout << e << " ";
}
cout << endl;
return 0;
}
范围for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围。
范围for与不同循环类似,可以使用continue来结束本次循环,也可以用break来跳出整个循环。
使用条件
1、范围for迭代范围必须是确定的
2、迭代的对象需要实现++和--操作。(迭代器得学习STL了解)
9. 指针空值nullptr
在C语言中NULL代表的是空值,而在C++11中引入了nullptr表示空值
现在的NULL在C++中被定义成一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
可以看到,NULL被定义为字面常量0,或者被定义为无类型指针(void*)的常量。
所以在C++中表示空值要使用nullptr,如果继续使用NULL可能会引发一些错误。
void A(int)
{
cout << "A(int)" << endl;
}
void A(int*)
{
cout << "A(int*)" << endl;
}
int main()
{
A(0);
A(NULL);
A((int*)NULL);
A(nullptr);
return 0;
}
如果按以前的思维,A(NULL)应该调用的应该是int*,但由于现在NULL被定义成宏表示0,所以违背本意,如果还想用NULL调用int*就需要进行强转,或者直接使用nullptr。
注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。(32位4字节,64位8字节)
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。