【C++初阶】C++基础(下)——引用、内联函数、auto关键字、基于范围的for循环、指针空值nullptr

目录

1. 引用

1.1 引用概念

1.2 引用特性

1.3 常引用

1.4 使用场景

1.5 传值、传引用效率比较

1.6 引用和指针的区别

2. 内联函数

2.1 概念

2.2 特性

3.auto关键字(C++11)

3.1 类型别名思考

3.2 auto简介

3.3 auto的使用细则

3.4 auto不能推导的场景

4. 基于范围的for循环(C++11)

4.1 范围for的语法

4.2 范围for的使用条件

5. 指针空值nullptr(C++11)

5.1 C++98中的指针空值


❀❀❀没有坚持的努力,本质上并没有多大的意义。


1. 引用

1.1 引用概念

引用 不是新定义一个变量,而 是给已存在变量取了一个 别名 ,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

 类型& 引用变量名(对象名) = 引用实体(注意:引用类型必须和引用实体同种类型

b叫做a的引用,b也可以叫做a的别名(abcd四个,只要有一个发生变化,其余都发生变化)

应用1

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

数据进行交换,可以不用传指针,可以用引用

应用2

#include <iostream>

typedef struct ListNode
{
	int val;
	struct ListNode* next;
}LTNode;

void LTPushBack_C(LTNode** pphead, int x)
{
	//C语言,单链表尾插需要传结构体的二级指针,因为需要改变首部地址
}

void LTPushBack_CPP(LTNode*& phead, int x)
{
	//C++中,用引用,仅仅需要传结构体地址
}

int main()
{
	LTNode* plist = NULL;
	//初始化
	LTPushBack_C(&plist, 1);
	LTPushBack_CPP(plist, 1);
	return 0;
}

也可以引用指针类型的 

注意:

typedef struct ListNode
{
	int val;
	struct ListNode* next;
}LTNode,*PLTNode;

void LTPushBack_CPP(LTNode*& phead, int x)
{
	//C++中,用引用,仅仅需要传结构体地址
}
//这两个等同
void LTPushBack_CPP(PLTNode& phead, int x)
{
	//C++中,用引用,仅仅需要传结构体地址
}

1.2 引用特性

代码展示:

#include <iostream>

int main()
{

	int a = 10;
	int& b = a;
	int& c = a;
	int& d = b;
	//一个变量可以多次引用
	int& e;//代码运行到这里会报错,因为引用在定义时必须初始化
	int m = 2;
	b = m;//b在前面已经引用了a,在这里并不是成为m的别名,而是把m的值赋值给b,然后此时abcd的值都是2
	return 0;
}
1. 引用在 定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体

1.3 常引用

const修饰的变量,只能读不能写(这里的权限,指的是读和写)

#include <iostream>

int main()
{
	int a = 0;
	int& b = a;//权限不变

	const int c = 2;
	int& d = c;//这里是错误的,权限不能被放大

	const int x = 3;
	const int& y = x;//这里是可以的,权限不变

	int m = 6;
	const int& n = m;//这里是可以的,权限缩小
	return 0;
}

取别名原则:对于引用类型,权限只能缩小,不能放大

临时变量具有常性

#include <iostream>

int main()
{
	int a = 10;
	int& b = a;

	const int& c = 20;//常量也可以取别名

	double d = 15.3;
	int f = d;//在这里,相当于f把自己的整数部分给一个临时变量,临时变量把值赋给f(临时变量具有常性)
	const int& e = d;//这里的e不是d的引用,而是临时变量的引用
	return 0;
}

1.4 使用场景

(1)做参数

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

可以不用传指针

(2)做返回值

代码1展示:(传值返回)

#include <iostream>

int Count()
{
	int n = 0;
	n++;
	return n;
}//n出了这个函数就被销毁了,所以是赋值给临时变量的
int main()
{
	int ret = Count();

	return 0;
}

函数返回过程,把返回的值n给一个临时变量,临时变量的类型就是函数类型(上述代码的int),临时变量再把值赋给主函数的ret。(临时变量即有一个拷贝)

代码2展示:(传引用拷贝)

#include <iostream>
int& Count()
{
	static int n = 0;//static不能去掉,如果去掉,就会涉及出现越界问题(因为空间被系统回收)
	n++;
	return n;
}//返回int&,说明有一个临时引用是int&类型,临时引用是n的别名
int main()
{
	int& ret = Count();//ret是临时引用的别名,
	return 0;
}

没有拷贝,效率高

如果函数返回时,出了函数作用域,如果返回对象还在 ( 还没还给系统 ) ,则可以使用引用返回, 如果已经还给系统了,则必须使用传值返回。(否则会出现越界问题)

注意:

#include <iostream>
int Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	const int& ret = Count();//因为是临时变量的别名,临时变量具有常性
	return 0;
}

1.5 传值、传引用效率比较

作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回, 而是 传递实参或者返回变量的一份 临时的拷贝 ,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。(传地址和传引用是差不多的)
传值和传引用在作为传参以及返回值类型上效率相差很大

1.6 引用和指针的区别

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

2. 内联函数

2.1 概念

inline 修饰 的函数叫做 内联函数 编译时 C++ 编译器会在 调用内联函数的地方展开 ,没有函数调用建立栈帧的开销,内联函数 提升程序运行的效率。
在函数前增加 inline 关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用

知识复习:写一个ADD的宏

 inline存在的意义:(1)解决宏函数晦涩难懂、容易写错(2)宏不支持调试

优点:(1)debug支持调试(2)不易写错,就是普通函数的写法(3)提升程序的效率

2.2 特性

1. inline 是一种 以空间换时间 的做法,省去调用函数额开销。所以 代码很长(大于10行) 或者有 循环 / 递归 的函数不适宜使用作为内联函数。
2. inline 对于编译器而言 只是一个建议 ,编译器会自动优化,如果定义为 inline 的函数体内有循环 / 递归等等,编译器优化时会忽略掉内联。
3. inline 不建议声明和定义分离 (头文件中,两个都写),分离会导致链接错误。因为 inline 被展开,就没有函数地址了,链接就会找不到。
知识点

宏的优缺点?
优点:
1. 增强代码的复用性。
2. 提高性能。
缺点:
1. 不方便调试宏。(因为预编译阶段进行了替换)
2. 导致代码可读性差,可维护性差,容易误用。
3. 没有类型安全的检查 。
C++ 有哪些技术替代宏
1. 常量定义 换用 const
2. 函数定义 换用内联函数

3.auto关键字(C++11)

3.1 类型别名思考

随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
1. 类型难于拼写
2. 含义不明确导致容易出错

 auto可以自动定义类型,根据等号后面的变量

C++中,typeid(A).name();可以知道A的类型是什么

3.2 auto简介

在早期 C/C++ auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量
C++11 中,标准委员会赋予了 auto 全新的含义即: auto 不再是一个存储类型指示符,而是作为一个新的类型 指示符来指示编译器, auto 声明的变量必须由编译器在编译时期推导而得
使用 auto 定义变量时 必须对其进行初始化 ,在编译阶段编译器需要根据初始化表达式来推导 auto 的实际类 。因此 auto 并非是一种 类型 的声明,而是一个类型声明时的 “占位符” ,编译器在编译期会将 auto 替换为 变量实际的类型

3.3 auto的使用细则

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

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

auto*定义的必须是指针类型

2. 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是 相同的类型 ,否则编译器将会报错,因为编译器实际只对 第一个类型进行推导,然后用推导出来的类型定义其他变量

auto意义之一:类型很长时,懒得写,可以让他自动推导。 

3.4 auto不能推导的场景

1. auto 不能作为函数的参数以及函数的返回值
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
2. auto 不能直接用来声明数组
void TestAuto()
{
 int a[] = {1,2,3};
 auto b[] = {4,5,6};
}

3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法

4. auto 在实际中最常见的优势用法就是跟以后会讲到的 C++11 提供的新式 for 循环,还有 lambda 表达式等进行配合使用。

4. 基于范围的for循环(C++11)

4.1 范围for的语法

C++11 中引入了基于范围的for 循环。 for 循环后的括号由冒号 分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围
void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
	//加&的原因是e是array内容的拷贝,所以改变e不是改变array里面的内容
	for (auto& e : array)
	{
		e *= 2;
	}
	//范围for,依次自动取arrar中的数据,赋值给e,自动判断结束
	for (auto e : array)//这里写int也可以
	{
		cout << e << " ";
	}
}
与普通循环类似,可以用 continue 来结束本次循环,也可以用 break 来跳出整个循环

4.2 范围for的使用条件

1. for 循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围 ;对于类而言,应该提供 begin end
方法, begin end 就是 for 循环迭代的范围。
注意:以下代码就有问题,因为 for 的范围不确定
void TestFor(int array[])
{
 for(auto& e : array)
 cout<< e <<endl;
}

这里的array是数组的首元素的地址,所以范围不定

2. 迭代的对象要实现 ++ == 的操作

5. 指针空值nullptr(C++11)

5.1 C++98中的指针空值


	//指针初始化
	int* p1 = NULL;
	int* p2 = 0;
	int* p3 = nullptr;//建议用这一种
C++98 中,字面常量 0 既可以是一个整形数字,也可以是无类型的指针 (void*) 常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0
注意:
1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr C++11 作为新关键字引入的
2. C++11 中, sizeof(nullptr) sizeof((void*)0) 所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr

猜你喜欢

转载自blog.csdn.net/m0_57388581/article/details/131950880