这里是oldking呐呐,感谢阅读口牙!先赞后看,养成习惯!
目录
5 语法糖
- 语法糖指在编程语言原有语法的基础上扩展的语法形式,语法糖的使用会让代码更加简洁,可读性更高
- 例如:中文中的"什么"和"啥"这样的关系,"什么"是"官方正经"的语法形式,而"啥"则就是语法糖,听着稍微偏门一些但确实更加简洁了
6 引用 – reference
- 引用是一种CPP中的语法糖,一般我们用它解决传址调用出现的频繁解引用的问题,它可以代替一部分指针的作用
6.1 引用在非函数定义中的使用
- 引用可以当作一个变量使用,但定义的引用在内存中并不存在,你可以理解为变量的一个别称,类似于
typedef
,只不过typedef
是给类型取别名,而引用是给变量取别名
int a = 10;
int& ref = a;
ref = 1; //此时a就被改成了1了
- 在以上代码,ref即a的引用,值得注意的是,这里ref的类型应该是
int&
,&
和int
应该是一体的- 编译过后
ref
会被替换成a
,原本的ref
和ref
的定义会直接消失
-
引用必须初始化,引用不初始化会报错
-
如果你想中途更改ref引用的目标变量,那请你把ref改成指针,因为引用ref压根就不能改,指针才能改
int a = 10;
int b = 20;
int& ref = a;
ref = b; //这样做相当于把b的值赋值给a,ref依旧引用a
6.2 引用在函数定义中的使用
- 引用在函数定义中用于替代一部分指针的作用,用于避免频繁解引用指针变量,使代码更加简洁美观,在函数定义中的引用只可以在函数内部使用
void add(int* ret, int* x, int* y)
{
*ret = *x + *y;
}
- 在使用了引用之后的效果
int main()
{
int x = 1;
int y = 2;
int ret = 0;
add(ret, x, y); //这里不需要传地址进去
return 0;
}
void add(int& ret, int& x, int& y)
{
ret = x + y; //这里的效果和传址调用是一样的
}
-
在这里引用一样可以改变原来变量的空间中的值
-
函数返回值中也可以添加引用,在类与对象终会非常有用,简单来说正常情况下返回值相当于是个常量,只能赋值/传参给别的变量/函数,但如果返回的是某个作用域不仅在本函数内的变量的引用,那这个返回值就可以被赋值,对返回值赋值!(如果作用域在本函数内,函数调用完之后该变量就会被销毁,此时返回引用的操作就会导致报错之类的)
-
比较复杂的情况我们以后再说
6.3 引用与const
-
我们来看看下面这个例子
-
这里是一个经典的权限放大的问题
-
原本
a
的权限为只读而非读写,现在你给a
整一个引用,那是不是就可以通过这个引用来修改原来a
的值了??? -
这里其实就是把
a
只读的权限放大为读写了,直接就会报错 -
不把权限放大的写法
int main()
{
const int a = 0;
const int& ra = a;
return 0;
}
-
也就是说,对于
const
对象只能用const
引用,即权限平移 -
当然,权限缩小也是可以的
int main()
{
int b = 0;
const int& rb = b;
return 0;
}
-
当然权限缩小仅限于
rb
,b
的权限依旧没变的,想咋改咋改 -
当然你甚至可以给一个数字取别名
const int& rc = 30;
- 甚至你可以给表达式取别名,临时对象具有常性,
(a + b)
在运算过后会生成一个临时变量
int a = 1;
int b = 2;
const int& rc = (a + b);
- 同样的,因为临时变量具有常性,所以在发生类型转换产生的临时变量也可以取别名
double a = 3.14;
const int& ra = a;
- 此时
ra
是3
的别名而不是a
的别名
6.4 引用与指针
- 引用只包含指针的部分功能,很多地方引用不能用还是需要指针
- 语法上引用是某个对象的别名,不开空间,而指针是开空间的
- 引用定义后必须初始化,指针建议初始化,但不强制
- 引用指定一个对象之后就不能再更改,而指针可以
- 引用可以直接访问被指向的对象,指针需要解引用才行
sizeof
下,引用是实际对象的大小,指针则是4/8字节- 指针容易出现野指针的问题,引用几乎不可能会出现这样的问题
7 inline
修饰 – 内联函数
- 用
inline
修饰的函数被称为内联函数,调用内联函数的时候会直接将函数在当前栈帧展开,意味着内联函数的栈帧就不建立了,直接展开直接用,不建立栈帧就可以提高效率 - 用
inline
修饰函数对于编译器来讲只是一个建议而已,编译器也完全可以不按照内联函数展开,而是按照正常函数创建栈帧,因为CPP压根就没有规定过这个,能不能用完全取决于编译器,内联函数适用于比较短小然后又要经常用的函数,对于递归来讲就不太适合了,很多编译器你在递归函数前修饰inline
也会被编译器忽略
一般情况下,五到十条语句就差不多了,再往上走编译器就会选择性忽略
inline
了,inline
和宏函数还是很像的,但区别在于宏函数是在预处理的时候对高级语言代码做手脚(替换),而inline
是在编译的时候对汇编代码做手脚(几乎也算是替换),避免产生函数栈帧,速度的确快,但如果频繁展开一个很长的函数,那完全展开之后的汇编代码就非常非常长,导致生成的可执行文件变得很大,所以说对于很多行代码的内联函数,编译器会忽略,而忽略之后就没必要展开,只需要建立栈帧一次就行,可执行文件就不会变得那么大,inline
其实本质上就是用空间换时间罢了,编译器自己会考虑一下性价比,再来看该不该用内联函数
- C在实现宏函数的时候会在预处理时展开,但宏函数实现非常复杂而且很容易出问题,而且不方便调试,CPP设计的内联函数就是为了替代宏函数而产生的
#define ADD(a, b) ((a)+(b))
inline int Add(int a, int b)
{
return (a + b);
}
int main()
{
cout << ADD(1, 2) << endl;
cout << Add(1, 2) << endl;
return 0;
}
以上这个例子的宏函数就必须要保证运算符优先级正确,保证替换后不成语句,就必须要加一堆括号,而且不能加
;
,写起来很复杂,而内联函数就是一个正儿八经的函数,按函数那套逻辑写就行了,既完成了减少函数开辟栈帧使效率提升的目的,还大大降低了错误发生的概率
- vs下被
inline
修饰的函数默认不会在debug下展开,如果要在debug下展开的话需要设置 inline
不建议定义和声明分离到两个文件,这样链接的时候会出错,因为inline
一旦被展开就没有函数地址,链接就会出错,所以一般情况下内联函数我们直接放进.h里头
8 nullptr(C++11)
- 原来我们在C中一直都用的
NULL
,这玩意就是个宏定义,在C下是(void*)0
即0被强转为了(void*)
,而CPP下则就被宏定义为0,但这玩意在CPP下有着巨大的坑,我们看例子
void func(int x)
{
cout << "int x" << endl;
}
void func(int* px)
{
cout << "int* px" << endl;
}
int main()
{
func(0);
func(NULL);
return 0;
}
CPP对于指针检查会更加严格一些,CPP不允许
(void*)
赋值给其他类型的指针,而C语言中就可以,所以哪怕在使用的时候强转成(void*)
也不一定会编译通过
-
因为CPP下指针使用的检查更加严格,我们发现CPP下的
NULL
和我们在C下的NULL
使用习惯不符,所以在CPP下我们新定义了一个nullptr
的玩意,专门用来代表空指针 -
nullptr
是一个CPP的一个关键字,它可以且只可以被隐式转换成其他任意类型的指针,但不能被转换成任何整数类型
void func(int x)
{
cout << "int x" << endl;
}
void func(int* px)
{
cout << "int* px" << endl;
}
int main()
{
func(0);
func(nullptr);
return 0;
}
- 所以我们在CPP下更推荐用
nullptr
而不是NULL