目录
- void Swap(int& left, int& right)
- {
- int temp = left;
- left = right;
- right = temp;
- }
- int main()
- {
- int a = 10;
- int b = 20;
- Swap(a, b);
- printf("%d %d\n", a, b);
- return 0;
- }
如上代码,left就是a的别名, right既是b的别名,只在前面加个 &即可
如下代码,a和ra地址相同,他们使用的是同一块内存空间,所以修改一个,都就变了
ra就是给已经存在的a取了个别名
- int main()
- {
- int a = 10;//实体
- //给a取别名,为ra
- int& ra = a;
- cout << a << endl;
- cout << &a << endl;
- cout << &ra << endl;
- //修改ra,a也会改变,因为是同一个东西
- ra = 100;
- cout << a << endl;
- return 0;
- }

注意:引用类型必须和引用实体是同种类型的
如果给上面代码加上一句: long& la = a; 那么就会报错 //error c2440:无法从Int转换为long
- void TestRef()
- {
- int a = 10;
- // 下面该条语句编译时会出错,没有初始化
- // int& ra;
- //一个变量可以多个引用,类似于一个人有多个绰号
- int& ra = a;
- int& rra = a;
- printf("%p %p %p\n", &a, &ra, &rra);
- }

将const类型的引用称为const引用
const修饰a,说明a是常量,常量不允许修改
- void TestConstRef()
- {
- const int a = 10;
- //int& ra = a; // 该语句编译时会出错,普通类型引用不能用,不然你引用了,修改ra会改变a,冲突
- const int& ra = a;
-
-
- // int& b = 10; // 该语句编译时会出错,b为常量
- const int& b = 10;
-
-
- double d = 12.34;
- //int& rd = d; // 该语句编译时会出错,类型不同
- const int& rd = d;
- }
-
-
如上,定义的是double d,按道理int& rd与之类型不同,是不同通过的,但前面加上 const 就可以正常运行。为什么?
编译器发现double和int之间可以发生隐式类型转换,于是重新创建一块整形空间,将d中的整形部分放在临时空间中。因为临时空间是编译器线上开辟的,用户不知道这块空间的名字,也不知道这块空间的地址,自然临时空间中的值就不能被修改,即:临时空间具有常性
1.为了简化代码直接给复杂的表达式取别名
- struct A
- {
- int a;
- };
-
- struct B
- {
- A aa;
- int b;
- };
- struct C
- {
- B bb;
- int c;
- };
- int main()
- {
- struct C cc;
- cc.c = 1;
- cc.bb.b = 2;
- cc.bb.aa.a = 3;
-
- //为了简化代码,最后效果一样
- B& bb = cc.bb;
- bb.b = 2;
- bb.aa.a = 3;
- return 0;
- }
2.引用作为函数的形参
这就是本次一开始的代码,在调用时,形参left是a的别名,right是b的别名
注意:如果不想通过形参修改外部的实参,可以将形参设置为const类型的引用
- void Swap(int& left, int& right)
- {
- int temp = left;
- left = right;
- right = temp;
- }
- int main()
- {
- int a = 10;
- int b = 20;
- Swap(a, b);
- printf("%d %d\n", a, b);
- return 0;
- }
3.用引用作为函数的返回值
- int& Add(int left, int right)
- {
- int temp = left + right;
- return temp;
- }
- int main()
- {
- int& ret = Add(1, 2);
- printf("%d\n", ret);
- printf("%d\n", ret);
- printf("%d\n", ret);
- return 0;
- }
按道理,一般认为结果都是3,实际运行如下

并且也有警告: warning C4172: 返回局部变量或临时变量的地址: temp
ret将add的返回值接收了之后,在程序中并没修改ret,但是后两次打印ret时结果发生变化,为什么?

规则:函数以引用的方式返回,一定不能返回函数栈上的空间
因为:当函数调用结束之后,栈上的空间就被回收了
如果在外部以引用的方式接收函数的返回值,外部的引用实际引用的就是一块非法的空间
正确的返回方式:返回的实体只要不随函数的结束而销毁
比如:全局变量,静态变量,引用类型的参数
- #include
- #include
- using namespace std;
-
- struct SeqList
- {
- int array[1000];
- int size;
- };
-
- void TestValue(SeqList s) //传值
- {
-
- }
- void TestPtr(SeqList* ps) //传地址
- {}
-
- void TestRef(SeqList& s) //引用
- {}
-
- void TestTime(int n)
- {
- SeqList s;
- //获取起始时间
- size_t beginVal = clock();
- for (int i = 0; i < n; ++i)
- {
- TestValue(s);
- }
- //获取结束的时间
- size_t endVal = clock();
- cout << "TestVal:" << endVal - beginVal << endl;
-
- //获取起始时间
- size_t beginPtr = clock();
- for (int i = 0; i < n; ++i)
- {
- TestPtr(&s);
- }
- size_t endPtr = clock();
- cout << "TestPtr:" << endPtr - beginPtr << endl;
-
- //获取起始时间
- size_t beginRef = clock();
- for (int i = 0; i < n; ++i)
- {
- TestRef(s);
- }
- size_t endRef = clock();
- cout << "TestRef:" << endRef - beginRef << endl;
- }
- int main()
- {
- TestTime(1000000);
- return 0;
- }

通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大
传地址和传引用的效率差不多,而引用比指针更加安全、代码可读性高,所以一般情况下推荐传引用 。
首先进行一段代码的比较,并查看反汇编程序

问题:
引用为别名,编译器不会给引用变量重新开辟内存空间,引用与其引用的实体共用同一份内存空间。但是发现:引用实际有空间,空间存放的是引用实体的地址,如何理解解释?
语法概念阶段:引用就是别名,编译器不会给引用变量开辟空间,方便理解
底层实现:要实现引用的技术,底层又把引用还原成指针---引用是语法层面的概念,在底层实际是没有引用的概念的,只有指针。
回到问题:指针和引用的区别
答:在底层实现上:引用和指针就是一样的,即引用在底层就是按照指针的方式实现的,因此引用实际上也是有空间的,内存存储的是其引用实体的地址。
引用和指针的不同点:
C++对c语言中的宏进行了优化
宏常量的优势:
1.可以达到一改全改的效果,提高了程序的扩展性
2.可以提高程序的可读性
- #define NUM 100
- int main()
- {
- int array[NUM];
- for (int i = 0; i < NUM; i++)
- {
- array[i] = i * 10;
- }
- for (int i = 0; i < NUM; i++)
- {
- cout << array[i] << " ";
- }
- cout << endl;
-
- return 0;
- }
宏常量的缺陷:
宏常量在定义时没有类型,在预处理阶段发生的替换,也不会进行类型检测
如下代码在3.14外加了双引号,运行报错却在第五行
- #define PI "3.14"
- int main()
- {
- double r = 2.0;
- cout << PI * r * r << endl;
- cout << PI * 2 * r << endl;
- return 0;
- }
因为宏常量有缺陷,因此在C++中,使用Const定义的常量代替宏
在C++中,被const修饰的变量不在是变量,而是一个常量
在C语言中,被const修饰的变量不是常量,而是一个不能被修改的变量
在C++中使用const修饰,会在定义PI时报错,清清楚楚,不会引起麻烦。
宏函数
优点:在预处理阶段展开(展开:就是用宏体替换宏使用的位置)少了函数调用的开销

如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。 查看方式:

- typedef char* pstring;
- int main()
- {
- const pstring p1; // 编译成功还是失败?
- const pstring* p2; // 编译成功还是失败?
- return 0;
- }
auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
- int main()
- {
- int a = 10;
- double r = 2.0;
-
- auto a1 = 5;
- auto r1 = 10.22;
- cout <<typeid(a1).name() << endl;
- cout <<typeid(r1).name() << endl;
- return 0;
- }
查看 a1,d1的类型

- int main()
- {
- int x = 10;
- auto a = &x;
- auto* b = &x;
- auto& c = x;
- cout << typeid(a).name() << endl;
- cout << typeid(b).name() << endl;
- cout << typeid(c).name() << endl;
- *a = 20;
- *b = 30;
- c = 40;
- return 0;
- }
如下,加不加*都是int*类型

- void TestAuto()
- {
- auto a = 1, b = 2;
- auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
- }
- // 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
- void TestAuto(auto a)
- {}
- void TestAuto()
- {
- int a[] = {1,2,3};
- auto b[] = {4,5,6};
- }
- void TestFor()
- {
- int array[] = { 1, 2, 3, 4, 5 };
- for(auto& e : array)
- e *= 2;
-
- for(auto e : array)
- cout << e << " ";
-
- return 0;
- }
for(auto e: 范围):e将来是范围中的每个元素的拷贝,即不能通过e修改范围中的数据
for(auto& e: 范围):e将来是范围中每个元素的别名,即可以通过e修改范围中的元素
- void TestFor(int array[])
- {
- for(auto& e : array)
- cout<< e <
- }
2.
迭代的对象要实现
++
和
==
的操作
。
十、 指针空值nullptr(C++11)
在c++11之前,统一使用NULL表示空值指针,下面为表示方法
- int main()
- {
- //c++98
- int* pq = NULL;
-
- //c++11
- int* p2 = nullptr;
-
- return 0;
- }
NULL
可能被定义为字面常量
0
,或者被定义为无类型指针
(void*)
的常量
。不论采取何种定义,在
使用空值的指针时,都不可避免的会遇到一些麻烦,
- void f(int)
- {
- cout<<"f(int)"<
- }
- void f(int*)
- {
- cout<<"f(int*)"<
- }
- int main()
- {
- f(0); //调用:f(int)
- f(NULL);
- f((int*)NULL);
- return 0;
- }
在 f (NULL) 中发现,并没有调用int*的版本,实际上调用的也是int的重载方法,原因是直接把NULL定位为0了。程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖
注意:
1.
在使用
nullptr
表示指针空值时,不需要包含头文件,因为
nullptr
是
C++11
作为新关键字引入的
。
2.
在
C++11
中,
sizeof(nullptr)
与
sizeof((void*)0)
所占的字节数相同。
3.
为了提高代码的健壮性,在后续表示指针空值时建议最好使用
nullptr
。
-
相关阅读:
SLAM从入门到精通(里程计的计算)
【Linux基本命令归纳整理】
mysql中geometry字段的查询和保存
11个Redis系列高频面试题,哪些你还不会?
【c++】刷题常用技巧
Oracle Primavera Unifier 23.4 新特征
启动bert-server报错TypeError: cannot unpack non-iterable NoneType object
js中的基础知识点
Lua表公共操作
【计算机视觉 | 目标检测】目标检测常用数据集及其介绍(七)
-
原文地址:https://blog.csdn.net/weixin_59215611/article/details/127674689