目录
命名空间
c语言有着多方命名冲突,会大量冲突的问题,官方库,第三方库都会产生冲突,所以在c++就有了命名空间
namespace 关键字
用来创建命名空间,一个命名空间定义了一个新的作用域
命名空间可以定义变量,函数,类型,命名空间
不同文件相同命名空间名会自动合并,并不会冲突
:: 域作用限定符
访问命名空间的成员
namespace jj//(命名空间名)
{
int num=1;
int add(int x,int y)
{
return x+y;
}
struct ST
{
int* a;
int top;
int capacity;
};
}
int main()
{
//使用方法,域作用限定符
printf("%d\n",jj::num);
printf("%d\n",jj::add(1,2));
//结构体的限定符加在struct后
struct jj::ST St;
//
return 0;
}
展开命名空间(全展开)
using namespace jj;
int num=0;
namespace jj//(命名空间名)
{
//命名空间可以定义变量,函数,类型
int num=1;
int add(int x,int y)
{
return x+y;
}
struct ST
{
int* a;
int top;
int capacity;
};
}
//展开命名空间(全展开)
using namespace jj;
int main()
{
printf("%d\n",num);
return 0;
}
展开虽然可以使用命名空间的成员,但是有危险,当有重命时编译器会无法判断到底使用哪里的,所以需要灵活使用
部分展开
using jj::num
namespace jj
{
int num=1;
//套娃
namespace kk
{
int num=3;
}
}
//部分展开
using jj::num;
int main()
{
printf("%d\n",num);
printf("%d\n",jj::kk::num);
return 0;
}
展开std标准库(c++标准库的命名空间)
头文件 iostream
using namespace std;
#include<iostream>
//全展
using namespace std;
cout
控制台输出,自动识别类型
cin
标准输入,自动识别类型
endl
换行符
<<
流插入运算符
>>
流提取运算
#include<iostream>
using std::endl;
using std::cin;
using std::cout;
int main()
{
int i=10;
cout<<i<<endl;
cin>>i;
cout<<i<<endl;
}
缺省参数
void Func(int n=1)
{
cout<<n<<endl;
}
int main()
{
Func(0);
//如果没有传参会使用缺省参数,可以认为是"备胎"
Func();
return 0;
}
全缺省
缺省参数传参时不能只传一两个,不传完,还得是从左向右传参,要么传要么就不传
void Func(int x=1,int y=2,int z=3)
{
cout<<x<<endl;
cout<<y<<endl;
cout<<z<<endl;
}
int main()
{
Func();
return 0;
}
半缺省
必须从右向左,给缺省值
void Func(int x,int y=2,int z=3)
{
cout<<x<<endl;
cout<<y<<endl;
cout<<z<<endl;
}
int main()
{
//这种就是错的
Func(3,,5);
//以下正确
Func(6);
Func(6,5);
Func(6,5,4);
return 0;
}
如果声明和定义分离同事给缺省参数,规定分离时声明给定义不给
函数重载
允许一个函数有多个功能
c语言不允许同名函数
函数名相同,同一个作用域,参数不同,顺序不同,类型不同,个数不同,才能构成函数重载
返回值不同不能构成重载,不知道调用哪个
为什么c语言不支持重载?
c语言编译过程
预处理
头文件展开,宏替换和删除,去注释条件编译
test.i(通过预处理后产生.i的中间文件)
编译,检查语法,生成汇编代码
test.s(汇编文件,由预处理得到的源代码文件)
汇编,将汇编代码生成二进制机器码,生成符号表(函数名与地址的映射)
test.o(目标文件)
链接,生成可执行程序
c语言在进行汇编时会直接用函数名当做符号表的名字,一旦有重复的函数名出现在符号表,就会导致混乱无法区分,所以c语言无法函数重载
但是c++有着函数名修饰规则
函数名修饰规则不同编译器有不同的修饰规则
如在linux中两个函数重载的函数名func在符号表中是这样的
void func(int i,double d)
void func(double d,int i)
void func(int i,double d) void func(double d,int i)
_Z4funcid
_Z4funcd
引用
取别名
int main()
{
int a=1;
int&b=a;
cout<<a<<endl;
cout<<b<<endl;
return 0;
}
结果是2,2.
这段代码中b是a的别名,b和a是同一个地址,改变b的值,a也会改变
根据这个特性可以实现指针的传址调用同样的效果
void Swap(int&a,int&b)
{
int c=a;
a=b;
b=c;
}
int main()
{
int x=1,y=0;
Swap(x,y);
cout<<x<<endl<<y<<endl;
return 0;
}
结果是0,1
&只有在类型和类型名之间才是取别名的意思
引用类型必须在定义的地方初始化,且类型必须相同
一个变量可以有多个引用
引用一个实体后,不能再引用其他实体,不能重定义
引用相较于传值调用效率较高
引用做返回值
int func()
{
int n=1;
return n;
}
int main()
{
int ret =func();
return 0;
}
这段代码并不是func直接返回n的值赋值给ret,在返回n之前func函数已经销毁了
所以在值小的时候会把值传给寄存器,再由寄存器来传给ret
当值过大时会在栈帧里开辟一片空间做返回值
这里统称为临时变量
传值传参
void func(int& x)
{
x++;
}
int main()
{
int a=0;
func(a);
return 0;
}
传值传参可以提高效率
输出型参数(修改形参会改变实参)
传引用返回
int& func()
{
int n=1;
return n;
}
int main()
{
int ret =func();
return 0;
}
这段代码返回了n的别名,但是同样的在返回之前栈帧已经销毁(还给系统)了,当这个值返回后ret可能为1,但也有可能为随机值,因为n的那片空间已经销毁,1可能存在也可能不存在
所以使用引用反回时,出了函数作用域,返回对象还在没有被销毁,那就可以使用引用返回,如果被销毁,那就必须使用传值调用
传引用返回同样可以提升效率
修改返回对象
int& func(int*& a)
{
a[0] = 0;
a[1] = 1;
a[2] = 0;
return a[0];
}
int main()
{
int* a = (int*)malloc(sizeof(int) * 3);
func(a)++;
cout << a[0] << endl;
cout << a[1] << endl;
cout << a[2] << endl;
free(a);
return 0;
}
结果是1,1,0
常引用
int func()
{
int i=1;
return i;
}
int main()
{
//1
const int a=0;
int& b=a;
//2
const int&c=a;
//3
int d=0;
const int& e=d;
//4
int f=0;
double& g=f;
//5
int& h=func();
}
权限放大
1.中a被修饰,a的值无法改变,但是给a取了个别名b,b却没有const的修饰,这就导致了b可以重新赋值,这两条代码无法执行
权限平移
2.中a又取了个别名c,c也被const给修饰了,c无法改变和a一样,这条语句可以执行
权限缩小
3.中给d取了别名e,但是e却被修饰,无法改变值,这两条语句可以执行
在引用中权限可以相等,缩小,但是不能放大
4.中给f取了个别名g,赋值会产生一个临时变量,临时变量具有常属性,把一个常量给一个变量,导致了权限的放大,所以用const修饰下g,权限平移这条语句就可以过了
5.中的问题是返回i的值时也是创建了临时变量,把一个常量赋给变量h,涉及了权限的放大
引用和指针的区别
引用语法上定义了一个变量的别名,指针是存储的变量地址
引用在定义时必须初始化,指针不需要
引用在定义一个别名后不能改变,在成为其他实体的别名
没有空引用,有空指针
sizoef计算引用时是引用类型的大小,计算指针时始终是地址空间字节数(64位占8字节,32位占4字节)
引用++是原名值加1,指针++是指针向后偏移一个类型的大小
有多级指针,没有多级引用
访问原数据不一样,指针需要解引用,引用靠编译器处理
引用相对指针更安全
int main()
{
int a=0;
int *b=&a;
int&c=a;
return 0;
}
从底层上看引用和指针是一样的
内联函数
在c语言中有着#define宏定义关键字
优点:增强代码复用性,提高性能
缺点: 坑多,可读性差,不方便调试,没有类型安全检查
在c++中就出现了内联函数关键字,替代了宏替换
inline关键字
内联函数的优势不用建立栈帧
将函数定义成内联函数
在被调用的地方展开函数(不用调用函数就使用函数的功能)
inline int add(int* x,int* y)
{
return x+y;
}
int main()
{
int x=1,y=1;
int z = add(x, y);
printf("%d\n", z);
return 0;
}
在汇编代码中可以看到并没有调用add函数
inline对于编译器而言是一个建议,编译器要判断函数的规模小(语句不长),不是递归,频繁使用,编译器才会执行,不然编译器可以忽略
代码膨胀
inline int add(int x, int y)
{
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
x++;
return x + y;
}
int main()
{
int x = 1, y = 1;
int z = add(x, y);
printf("%d\n", z);
return 0;
}
从图中可以发现由于函数语句过长,导致编译器忽略了内联函数
内联函数不能声明定义分离(分离在两个文件)会出现链接错误
因为在调用内联函数有只有声明包含(.h)无定义函数无法展开,只能去加载函数找地址又因为内联函数无法进入符号表,导致出错。
直接定义在.h文件即可解决
代码膨胀的后果程序变大
auto关键字
自动推导类型
int main()
{
int a=0;
auto b=a;
double f=3.14;
auto g=f;
return 0;
}
这种情况下auto没什么用处的
但是当类型很长的情况下,就非常有用了
如下:
int main()
{
int a=0;
auto b=a;
double f=3.14;
auto g=f;
return 0;
}
auto不能当做参数,也不能定义数组
nullptr
c++中NULL定义为了零 会遇到不可避免的错误
void func(int a)
{
cout << "int" << endl;
}
void func(int* a)
{
cout << "int*" << endl;
}
int main()
{
func(0);
func(NULL);
func(nullptr);
return 0;
}
//结果是int,int,int*
在重载函数中NULL被当做0使用
在c++中就nullptr
nullptr在c++中是作为关键字引入的
sizeof(nullptr)与sizeof((void*)0)的字节数相同