话不多说, 直接开始.
1. 引用
引用是C++中非常非常非常重要的语法, 以后会经常用到的
虽然非常重要, 但好在语法也是比较简单的
所谓引用, 其实就是给变量起了个别名 (就像我们每个人都有大名和小名一样)
void test9() {
//引用 : 类型 + & + 引用名称
//引用在定义时必须要初始化
int a = 10;
//a的别名
int& ra = a;
int& ra2 = a;
ra2 = 30;
ra = 100;
a = 1000;
cout << &a << endl;
cout << &ra << endl;
cout << &ra2 << endl;
int b = -1;
//引用定义之后, 不会再去更改实体的指向
// 这里是赋值操作, 并不是让ra作为b的别名
ra = b;
ra = -100;
上述代码我们可以看看调试器
可以看到, a, ra, ra2 的地址是相同的
同样可以看看上述代码的最后一部分
结果正如上面所写
下面说说const引用
void test10() {
//const 引用
const int a = 10;
const int& ra = a;
const int& r = 10;
double d = 2.0;
double& rd = d;
int c = d;
//d隐式类型转换, 生成一个临时的整型变量, rd2指向的位此临时变量
//临时变量具有常性, 所以要加个const
const int& rd2 = d;
}
我们看看调试结果
我们可以看到 c , d , rd2 的地址各不相同
此处 c是由d隐式类型转换来的, 其中间也生成了一个临时变量, 只不过没有指针指向它, 他给c赋值后被释放
而rd2就是指向d隐式转换时产生的临时变量
接下来我们回忆一下交换函数
之前C语言中通过指针进行交换
void Swap(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
现在C++中我们就可以用引用来写
void Swap(int& ra, int& rb) {
int tmp = ra;
ra = rb;
rb = tmp;
}
引用作为返回值时, 其生命周期一定要大于函数的生命周期
不然你临时变量在函数结束时销毁, 那你返回了个锤子0.0
//引用作返回值: 注意返回变量的生命周期一定要大于函数的生命周期
int& Add2(int a) {
return ++a;
}
int& d = Add2(a); //引用类型变量 d指向Add2()函数栈帧中的局部变量a, 当Add2函数结束之后变量a销毁
//如果是int d = Add2(a) 则没有问题, 因为是开辟了一个新的空间保存a的值
引用最大的作用还是提高代码的效率, 因为引用能省下拷贝付出的代价
下面我们具体看两个例子
struct A {
int array[10000];
};
void fun1(A a) {
}
void fun2(A& a) {
}
void t3() {
A a;
int n = 100000;
size_t begin = clock();
for (int i = 0; i < n; i++)
fun1(a);
size_t end = clock();
cout << "fun1(int): " << end - begin << endl;
begin = clock();
for (int i = 0; i < n; i++)
fun2(a);
end = clock();
cout << "fun2(int&): " << end - begin << endl;
}
A g;
A fun3() {
return g;
}
A& fun4() {
return g;
}
void t4() {
A a;
int n = 100000;
size_t begin = clock();
for (int i = 0; i < n; i++)
fun1(a);
size_t end = clock();
cout << "fun3(): " << end - begin << endl;
begin = clock();
for (int i = 0; i < n; i++)
fun2(a);
end = clock();
cout << "fun4(): " << end - begin << endl;
}
看到这里想必大家都明白为什么说引用能提高效率了吧~
下面说说引用的实现
//在底层 引用通过指针实现 定义一个引用类型变量相当于定义一个指针类型变量
//语法: 引用是别名, 不是指针, 没有发生拷贝
void t5() {
int a = 10;
int* pa = &a;
*pa = 20;
int& ra = a;
ra = 30;
}
我们可以看看底层的汇编代码
会发现引用和指针没有什么区别
2. 内联函数
内联函数: 编译器编译时, 会进行函数指令的展开, 没有栈帧的开销, 提高代码效率
下面直接上代码
//内联函数: 编译器编译时, 会进行函数指令的展开, 没有栈帧的开销, 提高代码效率
//代替宏函数使用
inline int ADD(int a, int b) {
return a + b;
}
//inline 只是一个建议, 编译器会根据实际情况进行判断, 如果代码简单, 直接展开, 代码复杂则不会展开
inline void fun5() {
for (int i = 0; i < 10000; i++) {
int a = i;
int b = a * i;
int c = b * i;
}
}
有几点需要注意 :
(1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜 使用作为内联函数。
(2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等 等,编译器优化时会忽略掉内联。
(3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会 找不到。
内联函数目前用到的比较少, 略作了解即可
宏的优缺点?
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
C++有哪些技术替代宏?
1 . 常量定义 换用const
2 .函数定义 换用内联函数
3. auto
void t7() {
//auto : C++中, 自动类型推导, 不代表具体类型
//编译时, 根据初始化表达式自动推导 (所以啊, 必须初始化)
//auto相当于类型的占位符, 具体类型在编译时进行推导
auto a = 2;
auto f = 3.5;
auto c = 'a';
auto d = 'a' + 'b';
cout << typeid(a).name() << endl; // 这句代码能打印出a的类型
cout << typeid(f).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto可以定义多个变量, 但是类型要一致(只做一次推导)
auto a1 = 2, c1 = 6;
auto a2 = 2.5, c2 = 5; //这样就不行了哦, 编译器偷懒, 只会推导一次
//auto定义指针时, 加不加*,没有区别
auto pa = &a;
auto* pa2 = &a;
cout << typeid(pa).name() << endl;
cout << typeid(pa2).name() << endl;
//定义引用时必须加&符号
auto& ra = a; //int&
auto ra2 = a; //int
}
auto不能推导的场景有 :
1. auto不能作为函数的参数
2. auto不能直接用来声明数组
4. 范围for
//范围for -> 要迭代的变量 : 迭代范围
void t8() {
int array[] = { 1, 2, 3, 4, 5, 6 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
cout << array[i] << " ";
cout << endl;
//范围for : 使用场景: 数据范围确定
for (auto e : array)
cout << e << " ";
cout << endl;
}
范围for要求数据的范围必须是确定的, 还有迭代器的内容(迭代器之后会有一些详细说明的)
范围for大家可以先用起来, 毕竟也比较方便嘛, 更重要的是不会越界
自己写for的话要很小心的确定范围, 一不留神就有越界的可能, 后果不堪设想, 范围for就不用这么操心了~
5. nullptr
void t9() {
int* p = NULL; // 等价于 int* p = 0;
}
void fun7(int a) {
cout << "fun7(int)" << endl;
}
//构成函数重载
void fun7(int* pa) {
cout << "fun7(int*)" << endl;
}
struct AS {
};
void t10() {
fun7(NULL); //编译器默认NULL为整型的0, 而不是指针空值
fun7((int*)NULL);// NULL有二义性: 指针空值 和 整型0
//nullptr: 指针空值, 意义明确
fun7(nullptr);
//nullptr可以隐式转换成任意的指针类型(内置和自定义)
int* p = nullptr;
char* p1 = nullptr;
float* p2 = nullptr;
AS* pas = nullptr;
//nullptr类型为: nullptr_t
cout << typeid(nullptr).name() << endl;
cout << sizeof(nullptr) << endl;
auto p3 = nullptr;
cout << typeid(p3).name() << endl;
}
这期就到这了~