[C++笔记]引用,内联

#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
#define N 10

//引用:给已经存在的变量取别名
//可以减少对指针的使用
//引用在语法层理解为没开新空间,只是对已有空间取新名称
void ReferenceSample() {
	int a = 10;
	//↓这里&是引用,不是取地址!
	int& b = a;//引用必须初始化(指定目标),若只写int& b;是无意义的
	int& c = a;//一个变量可有多个引用
	int& d = b;//与int& d = a;等效
	a = 20;
	b = 30;
	int e = 20;
	b = c;//这里是赋值而不是引用,b已是a的别名
}
void ReferenceSwap(int& r1, int& r2) {//r1作为x的别名,r2作为y的别名
	int tmp = r1;
	r1 = r2;
	r2 = tmp;
//tip:传值,传址,传引用类型不同,可构成函数重载,但调用时会出现歧义,不知道调传值还是传引用函数
}
//单链表传二级指针的方法除了可以用返回值的方式代替,也可以用传指针的引用来代替
//void SListPushBack(SLTNode*& phead,SLTDataType x);
//SLTNode* plist=NULL;
//SListPushBack(plist,1);
int Add(int a,int b) {
	//传值返回:所有传值返回都会生成一个拷贝
	int c = a + b;
	return c;
	//这里返回到main的实际上是局部变量c在一个临时变量中的拷贝,因为返回时c栈帧已销毁
	//若c较小(4-8字节),一般是寄存器充当临时变量
	//若c较大,临时变量放在调用该函数的栈帧中
}
int& ReferenceAdd(int a, int b) {
	int c = a + b;
	return c;
}
//传引用返回:不生成c的拷贝,直接返回c的引用(这里存在非法访问,但由于是读操作,一般不报错)
//若本函数栈帧销毁后清理了空间,则取到的c是随机值,具体取决于编译器的实现
//vs销毁栈帧不清空间,除非free啥的
//!所以通常不建议传引用返回,不要返回局部变量的引用!
//若函数返回时出了函数作用域,返回对象还没还给系统(全局变量等),则可使用传引用返回(效率比传值高些)
//若已还给系统,则必须使用传值返回
int& RefReturn1() {
	int* p = (int*)malloc(4);
	return *p;
	//p是局部变量,栈帧销毁后p就不在了,所以不能返回p
	//p指向的空间*p在栈帧销毁后依然存在,所以可以返回*p
}
//另:临时变量具有常性,不能被修改
//传引用返回,可读可写,调用者可以修改返回对象:
int& RefReturn2(int i) {
	static int a[N];
	return a[i];
}
void RefReturn2Test() {
	for (size_t i = 0; i < N; ++i) {
		RefReturn2(i) = 10 + i;//写
	}
	for (size_t i = 0; i < N; ++i) {
		cout << RefReturn2(i) << " ";//读
	}
}//后续在成员函数的学习中会进一步接触引用作返回

//常引用
void ReferenceConst(){
	//const int a = 10;
	//int& b = a;
	//无法通过编译,因为权限放大了:const要求只读,而引用要求读与写
	const int a = 10;
	const int& b = a;
	//权限一致就可以了
	int c = 10;
	const int& d = c;
	//权限缩小也是可以的
}
//假设x是一个大对象或深拷贝对象,则尽量用引用传参以减少拷贝
//const Type&可以接收各种类型(变量/常量//类型转换的量等)的对象
//若f函数中不改变x,则尽量用const引用传参

//*引用的主要作用体现在传参和传返回值:
//	1.在一些场景下可显著提高性能(大对象,深拷贝对象)
//	2.作输出型参数和输出型返回值,使得有些场景中形参的改变可以影响实参/改变返回对象

//引用和指针的不同点(需重点理解,不是背诵):
//1. 引用在定义时必须初始化,指针没有要求
//2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
//3. 没有NULL引用,但有NULL指针
//4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
//5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
//6. 有多级指针,但是没有多级引用
//7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
//8. 引用比指针使用起来相对更安全
//指针使用起来比引用更复杂更易出错,虽然还是有指针能干但引用不能干的事(比如实现链表)

//内联函数
//对于频繁调用的函数,为减少消耗,C使用宏,宏是一种替换机制(没有传参):
//#define ADD(x,y) ((x)+(y))
//↑不可以分号结尾(因为分号也会被替换进去),而且必须防止使用时优先级出问题(使用括号)
//宏的缺点:
//	1.不能调试
//	2.没有类型安全的检查
//	3.某些场景下非常复杂,易出错且难掌握
//		比如实现ADD宏函数:#define ADD(x,y) ((x)+(y))
//		↑((x)+(y))的两层括号的必要性与意义,都是表达式的结合问题相关的,易错
//
//C++推荐使用const和enum代替宏常量,使用内联函数代替宏函数
//内联函数:在函数前加inline
inline int Add(int x, int y) {
	int ret = x + y;
	return ret;
}
//然后VS在release版本中inline的函数就会在调用处展开
//debug下默认不展开,可以在属性\C / C++\优化\内联函数扩展设置成只适用于inline来要求展开
//再查看反汇编就可以看到没再call这个函数了,替换成了函数实现的指令
//*注意:inline是以空间换时间(不是内存空间,是编译出来的可执行程序占用的空间),
//,递归函数和长函数不宜内联(默认10行以上,具体取决于编译器)
//*inline不建议声明和定义分离,展开后链接时会找不到函数地址,会导致链接错误
//*inline对于编译器而言只是一个建议,编译器会根据长度或递归等自行判断是否忽略内联建议

int main() {
	ReferenceSample();

	int x = 0, y = 1;
	ReferenceSwap(x, y);//这里是传引用(可改变实参),不是传值也不是传址

	int ret1 = Add(1, 2);
	cout << ret1 << endl;

	int& ret2 = ReferenceAdd(1, 2);
	cout << ret2 << endl;

	RefReturn2Test();

	Add(1, 2);

	return 0;
}

//额外知识点:
//C程序调用C++的库,在C++程序中加extern"C"
//C程序调用C++的库,在C++库中加extern"C"

你可能感兴趣的:(笔记,c++)