1️⃣命名空间存在的意义是防止命名冲突
2️⃣一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
我们知道变量、函数有很多,这些变量和函数大量存在于全局作用域中,会导致命名冲突,用strlen函数为例。
如果要定义一个以strlen为变量名的变量,并且引了<string.h>,就会发生以下情况:
#include<string.h>
int strlen = 0;
int main()
{
return 0;
}
error C2365: “strlen”: 重定义;以前的定义是“函数”.
如果要解决这一问题,就要用到命名空间
1️⃣命名空间中的内容,既可以定义变量,也可以定义函数
// yyh为命名空间的名称
namespace yyh
{
//strlen为变量名
int strlen = 0;
int Add(int x, int y)
{
return x + y;
}
}
2️⃣命名空间可以嵌套
// yyh为命名空间的名称
namespace yyh
{
int strlen = 0;
int Add(int x, int y)
{
return x + y;
}
namespace yyh1
{
int a = 0;
}
}
3️⃣同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中(不同文件也行)
// yyh为命名空间的名称
namespace yyh
{
int strlen = 0;
int Add(int x, int y)
{
return x + y;
}
}
namespace yyh
{
int a = 0;
}
但是如果我们在主函数里使用就会发现会出问题
// yyh为命名空间的名称
namespace yyh
{
int strlen = 0;
int Add(int x, int y)
{
return x + y;
}
}
int main()
{
printf("%d", Add(1, 2));
return 0;
}
error C3861: “Add”: 找不到标识符
命名空间的三种正确用法:
1️⃣指定命名空间
int main()
{
printf("%d", yyh::Add(1, 2));
return 0;
}
这个方法写起来很麻烦,但是是最规范的方式
2️⃣使用using(对部分展开)
using yyh::Add;
int main()
{
printf("%d", Add(1, 2));
return 0;
}
这种方法可以对库里面常用的函数展开,比如:
using std::cout;
using std::endl;
项目中经常用
3️⃣使用using namespace
using namespace yyh;
int main()
{
printf("%d", Add(1, 2));
return 0;
}
这种方法用起来很简单,比如用using namespace std;但是如果我们自己定义的东西跟库起冲突,就没办法解决了,所以在项目中不推荐
输入并输出:“hello”
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
int main()
{
char a[10];
cin >> a;
cout << a << endl;
return 0;
}
跟C语言一样都需要头文件#include<iostream>
可以发现跟C语言的区别就是C++不用用格式来输入输出,如%d, %s。
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该
默认值,否则使用指定的实参。
void Fun(int a = 1)
{
cout << a << endl;
}
int main()
{
Fun();
Fun(5);
return 0;
}
结果:
1
5
全部参数都给了缺省值
void Fun(int a = 1, int b = 1)
{
cout << "a = " << a << " ";
cout << "b = " << b << endl;
}
int main()
{
Fun();
Fun(2);
Fun(5, 5);
return 0;
}
结果:
a = 1 b = 1
a = 2 b = 1
a = 5 b = 5
部分参数给了缺省值
void Fun(int a, int b = 1, int c = 1)
{
cout << "a = " << a << " ";
cout << "b = " << b << " ";
cout << "c = " << c << endl;
}
注意:
1️⃣半缺省参数必须从右向左依次给出,不能间隔
2️⃣缺省参数不能同时出现在函数的声明和定义中
例如在.h文件中有void Fun(int a = 1, int b = 1);
那么在.cpp文件中就只能void Fun(int a, int b){}
3️⃣缺省值必须是常量或者全局变量
C语言中不允许定义同名的函数
而C++可以用函数重载定义同名的函数
要求:
形参列表(参数个数 或 类型 或 顺序)必须不同
正确的函数重载:
int fun(int a, int b)
{
return a + b;
}
double fun(double a, double b)
{
return a + b;
}
int fun(int a, int b)
{
return a + b;
}
int fun(double a, double b)
{
return a + b;
}
错误的函数重载:
int fun(int a, int b)
{
return a + b;
}
int fun(int b, int a)
{
return a + b;
}
int fun(int a = 1, int b = 1)
{
return a + b;
}
double fun(int a = 2, int b = 3)
{
return a + b;
}
但是有的构成重载但是不能用:
void Fun(int a = 0, int b = 1) {}
void Fun(int a = 0, int b = 1, int c = 2) {}
int main()
{
Fun(1, 2);
return 0;
}
虽然语法行得通,但是这样会导致编译器不知道调那个函数
首先想一个问题:为什么C++支持函数重载,而C语言不支持?
1️⃣预处理:头文件展开,宏替换,去掉注释,条件编译
2️⃣编译:检查语法,生成汇编代码(指令)
3️⃣汇编:把汇编代码转换成二进制机器码
4️⃣链接:把文件链接到一起,生成可执行程序
而如果在头文件调用函数,它会在链接时找到定义(地址)
//Test.h
int Add(int a, int b);
double Add(double a, double b);
//Test.cpp
int Add(int a, int b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
//main.cpp
int main()
{
Add(1, 2);//call Add(地址)
Add(1.1, 1.2);//call Add(地址)
return 0;
}
在C++中,所有的函数名都会被处理,例如上面的代码,第一个Add会变成_Z3addii
,第二个就会变成_Z3adddd
,注意不光重载会改变名字,所有的函数都会按照函数名修饰规则改变
但是C语言中,就没有函数名修饰规则,函数名为Add,就会那Add去找,所以不支持重载
假设我们在C++中写了一个tcmalloc
函数,而我们想在C语言中调用,在C++中函数会经过函数名修饰规则变成_Z8tcmallocui
,而我们在C++文件中加上extern "C" void* tcmalloc(unsigned int n);
,C++中符号表就会把_Z8tcmallocui
变成tcmalloc
(用C的规则),这样就可以调用了。
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
用法:
int main()
{
int a = 10;
int& b = a;//b为a的引用
return 0;
}
1️⃣引用在定义时必须初始化
2️⃣一个变量可以有多个引用
3️⃣引用一旦引用一个实体,再不能引用其他实体
int main()
{
int& a;//error1
int b = 0;
int& c = b;
int d = 1;
c = d;//这里不是让c变成d的别名,而是把d的
//值赋值给c(也就是b)
return 0;
}
当变量被const修饰后,如果要引用,他的引用也要用const修饰(权限不能扩大,可以缩小)
int main()
{
const int a = 1;
int& b = a;//error,权限放大
const int& b = a;//correct
return 0;
}
int main()
{
int a = 10;
const int& b = a;//correct, 权限缩小
return 0;
}
还有一点要注意:
int main()
{
double b = 1.3;
int c = 1;
c = b;//隐式类型转换
return 0;
}
c = b这种类型转换的过程会有个int类型的临时变量,把b的值赋值给临时变量,c再接收。
而这个临时变量具有常属性
int main()
{
double b = 1.3;
int c = 1;
c = b;
double& rc = c;//error
const double& rc = c;//correct
return 0;
}
这里的rc是c的临时变量的别名,强制类型转换也是一个道理
我们知道交换两个值的函数在C语言中要使用指针,不然交换的只是临时变量,而在C++中,我们可以使用引用
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
1️⃣传值返回
先来看一个场景
int Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int ret = Add(1, 2);
const int& Ret = Add(1, 2);
return 0;
}
这里Add函数返回c的值并不是直接返回,而是把c的值先给一个临时变量,再把临时变量返回。因为Add函数栈帧销毁后c也会消失,而临时变量不属于这个栈帧。
2️⃣传引用返回
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int ret = Add(1, 2);
return 0;
}
这里Add函数返回c的时候也会返回临时变量,但是临时变量的类型是int&,他返回的是c的
引用(别名),但是函数结束后栈帧销毁,如果让ret接收,就会非法访问空间。
如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已
经还给系统了,则必须使用传值返回。
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是
传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是
当参数或者返回值类型非常大时,效率就更低
所以只要符合条件,尽量选择传引用做参数和返回值
引用和指针的不同点:
1️⃣引用在定义时必须初始化,指针没有要求
2️⃣引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
实体
3️⃣没有NULL引用,但有NULL指针
4️⃣在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
4个字节)
5️⃣引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
6️⃣有多级指针,但是没有多级引用
7️⃣访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8️⃣引用比指针使用起来相对更安全
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,
内联函数提升程序运行的效率。
inline int Add(int a, int b)
{
return a + b;
}
int main()
{
Add(1, 2);
return 0;
}
在C语言中为了避免栈帧的消耗,一些小函数可以用宏函数,在预处理阶段展开,那我们为什么要有内联函数呢(宏函数的优缺点)?
优点:
1️⃣增强代码的复用性
2️⃣提高性能
缺点:
1️⃣不支持调试
2️⃣宏函数语法复杂,易出错
3️⃣没有类型安全检查(直接替换)
例如写一个ADD的宏函数:#define ADD(x, y) ((x) + (y))
为什么要这么多括号?
防止优先级出现错误
#define ADD(x, y) ((x) + (y))
int main()
{
int c = ADD(1 | 2, 1 | 3);
int d = ADD(1, 2) * 3;
return 0;
}
1️⃣如果代码很长或者有循环或递归的情况,不适宜用内联函数
2️⃣inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联
3️⃣ inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到
int main()
{
int a = 0;
auto b = a;
return 0;
}
可以根据a的类型自动推出b的类型
1️⃣auto与指针和引用结合起来使用,用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
判断a,b,c的类型:
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
int& y = x;
auto& c = y;
return 0;
}
a: int*
b: int*
c: int (y的类型也是int)
1️⃣auto不能作为函数的参数
2️⃣auto不能直接用来声明数组
void fun(auto x)//error1
{}
int main()
{
int a[] = { 1, 2, 3 };
auto b[] = { 1, 2, 3 };//error2
return 0;
}
int main()
{
int a[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
cout << a[i] << " ";
}
cout << endl;
//范围for
for (auto e : a)
{
cout << e << " ";
}
return 0;
}
这里要注意的时e是a[i]的拷贝,改变e不会改变数组里面的元素,要改的话可以加引用