C++入门语法(二)

引用

int& pa = a; //c++的引用,就是起别名,a或ra都能访问这块空间
//类型& 引用变量名(对象名) = 引用实体
//引用类型必须和引用实体是同种类型;

int* pa = &a; //c的指针,符号一样,要区分开

初始化

  1. 引用在定义时必须初始化int& ra;//err
  2. 一个变量可以有多个引用 int& x = a; int& y = a; int& pa = a;
  3. 引用一旦引用一个实体,再不能引用其他实体(记住对引用变量进行赋值时,改的是引用和引用指向内容的值,而不是它的指向,因为引用不能再 引用其他实体)【int a=10; int& ra=a; int b=20; ra=b;这个代码其实是a=b;】;

使用场景

有些场景有引用就可以不用指针,相当于指针的简化,但引用不能取代指针。

//1、做参数
void swap(int& m, int& n)//把实参的地址传给形参
{
	int tmp = m;
	m = n;
	n = tmp;
}
int main()
{
	int x = 10, y = 20;
	swap(x, y);//m是x的别名,n是y的别名
}

//2、做返回值 引用返回,需为static
int& Count()
{
	static int n = 0;//n在静态区,如果n不用static修饰却引用返回就会出错
	n++;
	return n;
}
int main()
{
	int ret = Count();//main函数栈帧中接收返回值的变量ret会建立Count函数栈帧【栈是向下增长的】,ret是n的别名
	cout << ret << endl;
	cout << ret << endl;
	return 0;
}
//注意:引用返回:临时变量的值是int&,没有额外开辟空间,相当于直接返回了n,此时函数栈帧内创建的变量需为static放在静态区的,不然普通的变量在函数栈帧里直接返回就会出现越界访问
//传值返回:传值调用的过程是 把n的值交给一个寄存器,寄存器再把这个值给ret。因为当n不是静态变量时就是存在于函数栈帧里的,当函数栈帧销毁时n不存在了无法直接给ret,想要给ret只能进入main栈帧,但是不能同时在两个栈帧里,所以必须要用到临时变量(寄存器或者返回值所在的函数栈帧)临时变量的类型是int,开辟了一个空间。可以不用static

//这个是返回值引用,主函数用引用接收
int& Count()
{
	static int n = 0;//而n在静态区
	n++;
	return n;
}
int main()
{
	int& ret = Count();//相当于n的引用再引用,会访问2次n的空间,第一次是Count返回时,第二次是ret初始化时。如若后续要重新开辟栈帧,这块空间就会被初始化。而n是在静态区,生命周期是整个程序
	cout << ret << endl;
	return 0;
}

内存空间销毁,意味着 空间还在,只是没有使用权,所存的数据不被保护,还能访问,只是我们读写的数据都是不确定的。故出了函数作用域,返回变量存在,才能引用返回。

请考虑一下单链表的尾插当时是使用二级指针来完成的,现在就可以用引用

void SLitstPushBack(struct SListNode** pphead, int x);
void SLitstPushBack(struct SListNode*& phead, int x);//引用

引用返回的意义:减少拷贝,提高效率;修改返回值。

关于常引用

1、使用时要注意指针和引用的权限可以平移和缩小,但不能放大

int a = 10;
int& ra = a;//权限的平移

const int a = 10;
int& ra = a;//err,权限的放大

int a = 10;
const int& ra = a;//权限的缩小

const int a = 10;
const int& ra = a;//权限的平移

使用场景:假如在函数传参时,只想减少拷贝,不用修改实参值,就可以在函数声明时写成常引用。

2、类型不同时要加const

int main()
{
	double b = 12.13;

	const int& rb = b;

	cout << "rb=" << rb << endl; // rb=12
	cout << "&rb " << &rb << endl; // &rb 012FFE5C
	cout << "b=" << b << endl; // b=12.13
	cout << "&b=" << &b << endl; // &b=012FFE74
	return 0;
}

类型转换(比如截断、整型提升、强制类型转换)的过程中,均会产生一个临时变量。在上述代码,a在转换成int时,会产生一个int类型的临时变量,临时变量具有常性不可修改,故只能给rra,rra引用的是临时变量,所以b的地址和rb的地址是不一样的。

3、传值返回,用常引用接收

int Count();
int main()
{
	const int& ret = Count();
	return 0;
}

因为传值返回,返回的是个临时变量,具有常性.

4、语法和底层

语法上引用不开空间,实际上底层实现上是开空间的,是使用指针实现的。

引用和指针的区别

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址;
  2. 引用在定义时必须初始化,指针没有要求;
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体;
  4. 没有NULL引用,但有NULL指针;
  5. sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节);
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全。

引用构成重载,但是实际用的时候用不了,会引起歧义。

auto

自动识别类型,自动判断结束,自动迭代。

使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

//场景1
std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange",
"橙子" },{"pear","梨"} };
std::map<std::string, std::string>::iterator it = m.begin();
//auto it = m.begin(); // 当类型过长,直接用auto

//场景2 范围for遍历
//以前写的
int array[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
		cout << array[i] << " ";

	cout << endl;

// 用auto写的,依次取array中数据赋值给e 自动判断结束,自动迭代
for (auto e : array)
{
    cout << e << " ";
}
cout << endl;

typeid(a).name()可以拿到变量的类型,以字符串形式输出。

使用细则

1、auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须
加&

int x = 10;
auto a = &x; // a是int*
auto* b = &x; // b是int*
auto& c = x; // c是int

2、auto不能做参数,auto不能直接用来声明数组

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}

3、在同一行对同一类型的变量同时进行初始化

auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同

内联函数

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方用函数体替换函数的调用。没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

与宏的区别

在c语言中可以用宏函数;在cpp中用inline函数。但是宏的缺点是无法调试,没有类型安全的检查,容易写错(优先级,传表达式等都可能存在问题)。

C++有哪些技术替代宏?

  1. 常量定义 宏常量用const enum替换;
  2. 短小函数定义 用内联函数替换。

特性

1、inline是一种以空间(这里的空间指的是编译出来的可执行程序的大小)换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。

2、inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。

3、在debug版本下,内联不会展开,此时转成反汇编视图,也会看到call(建立函数栈帧的过程)。因为debug模式下,编译器默认不会对代码进行优化。

4、inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到。预处理和编译都能过。正常使用的内联函数,在调用时不会去call内联函数的地址,故内联函数定义所在的cpp文件中,看到inline,编译器认为这个内联函数在当前文件会直接展开,所以符号表里就不会存内联函数的地址。

预编译后,头文件在各文件展开。在链接前,各cpp文件不会相互联系。主文件里知道这个函数是inline,但是主文件里没有该函数的定义,想展开也展不开。那只能链接的时候去找,但是链接的时候没有该函数的地址也找不到它,所以会报链接错误。有声明没定义才会去找链接

所以内联函数直接在.h文件定义即可。

指针空值nullptr

NULL实际是一个宏,在传统的C头文件(stddef.h)中

#ifndef NULL
#ifdef __cplusplus
#define NULL 0 // NULL被定义为字面常量0
#else
#define NULL ((void *)0) // 被定义为无类型指针(void*)的常量
#endif
#endif

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦。

故C++11引入nullptr来解决问题。在C++11中,sizeof(nullptr)sizeof((void*)0)所占的字节数相同。

nullptr 就是 (void*)0

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值