C++入门基础语法 2 (引用, 内联函数, auto, 范围for, nullptr)

话不多说, 直接开始.

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;
}

这期就到这了~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殇&璃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值