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

目录

[1. 引用](#1. 引用)

[1.1 引用概念](#1.1 引用概念)

[1.2 引用特性](#1.2 引用特性)

[1.3 常引用](#1.3 常引用)

[1.4 使用场景](#1.4 使用场景)

[1.5 传值、传引用效率比较](#1.5 传值、传引用效率比较)

[1.6 引用和指针的区别](#1.6 引用和指针的区别)

[2. 内联函数](#2. 内联函数)

[2.1 概念](#2.1 概念)

[2.2 特性](#2.2 特性)

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

[3.1 类型别名思考](#3.1 类型别名思考)

[3.2 auto简介](#3.2 auto简介)

[3.3 auto的使用细则](#3.3 auto的使用细则)

[3.4 auto不能推导的场景](#3.4 auto不能推导的场景)

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

[4.1 范围for的语法](#4.1 范围for的语法)

[4.2 范围for的使用条件](#4.2 范围for的使用条件)

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

[5.1 C++98中的指针空值](#5.1 C++98中的指针空值)


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


1. 引用

1.1 引用概念

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

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

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

cpp 复制代码
void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

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

cpp 复制代码
#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;
}

也可以引用指针类型的

注意:

cpp 复制代码
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 引用特性

代码展示:

cpp 复制代码
#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修饰的变量,只能读不能写(这里的权限,指的是读和写)

cpp 复制代码
#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;
}

取别名原则:对于引用类型,权限只能缩小,不能放大
临时变量具有常性

cpp 复制代码
#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)做参数

cpp 复制代码
void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

可以不用传指针

(2)做返回值

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

cpp 复制代码
#include <iostream>

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

	return 0;
}

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

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

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

注意:

cpp 复制代码
#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. 提高性能。
    缺点:
  3. 不方便调试宏。(因为预编译阶段进行了替换)
  4. 导致代码可读性差,可维护性差,容易误用。
  5. 没有类型安全的检查 。
    C++ 有哪些技术替代宏
  6. 常量定义 换用 const
  7. 函数定义 换用内联函数

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 不能作为函数的参数以及函数的返回值
cpp 复制代码
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
  1. auto 不能直接用来声明数组
cpp 复制代码
void TestAuto()
{
 int a[] = {1,2,3};
 auto b[] = {4,5,6};
}
  1. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
  2. auto 在实际中最常见的优势用法就是跟以后会讲到的 C++11 提供的新式 for 循环,还有 lambda 表达式等进行配合使用。

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

4.1 范围for的语法

C++11 中引入了基于范围的for 循环。 for 循环后的括号由冒号 " " 分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围

cpp 复制代码
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 的范围不确定
cpp 复制代码
void TestFor(int array[])
{
 for(auto& e : array)
 cout<< e <<endl;
}

这里的array是数组的首元素的地址,所以范围不定
2. 迭代的对象要实现 ++ == 的操作

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

5.1 C++98中的指针空值

cpp 复制代码
	//指针初始化
	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

相关推荐
可均可可20 分钟前
C++之OpenCV入门到提高004:Mat 对象的使用
c++·opencv·mat·imread·imwrite
杨荧36 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
白子寰42 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
小芒果_011 小时前
P11229 [CSP-J 2024] 小木棍
c++·算法·信息学奥赛
gkdpjj1 小时前
C++优选算法十 哈希表
c++·算法·散列表
王俊山IT1 小时前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。1 小时前
c++多线程
java·开发语言
-Even-1 小时前
【第六章】分支语句和逻辑运算符
c++·c++ primer plus
小政爱学习!1 小时前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript