目录
[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.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:
cppvoid 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; }
也可以引用指针类型的
注意:
cpptypedef 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.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 引用和指针的区别
引用和指针的不同点 :
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用 在定义时 必须初始化 ,指针没有要求
- 引用 在初始化时引用一个实体后,就 不能再引用其他实体 ,而指针可以在任何时候指向任何一个同类型实体
- 没有 NULL 引用 ,但有 NULL 指针
- 在 sizeof 中含义不同 : 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占4个字节 )
- 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同, 指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
语法的角度:引用是一个别名,没有额外开空间,指针存储的是地址,需要开一个4/8字节的空间;但是从底层的角度,是一样的方式实现的(汇编代码是一致的)
2. 内联函数
2.1 概念
以 inline 修饰 的函数叫做 内联函数 , 编译时 C++ 编译器会在 调用内联函数的地方展开 ,没有函数调用建立栈帧的开销,内联函数 提升程序运行的效率。
在函数前增加 inline 关键字 将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
知识复习:写一个ADD的宏
inline存在的意义:(1)解决宏函数晦涩难懂、容易写错(2)宏不支持调试优点:(1)debug支持调试(2)不易写错,就是普通函数的写法(3)提升程序的效率
2.2 特性
- inline 是一种 以空间换时间 的做法,省去调用函数额开销。所以 代码很长(大于10行) 或者有 循环 / 递归 的函数不适宜使用作为内联函数。
- inline 对于编译器而言 只是一个建议 ,编译器会自动优化,如果定义为 inline 的函数体内有循环 / 递归等等,编译器优化时会忽略掉内联。
- inline 不建议声明和定义分离 (头文件中,两个都写),分离会导致链接错误。因为 inline 被展开,就没有函数地址了,链接就会找不到。
知识点 :
宏的优缺点?
优点:
- 增强代码的复用性。
- 提高性能。
缺点:- 不方便调试宏。(因为预编译阶段进行了替换)
- 导致代码可读性差,可维护性差,容易误用。
- 没有类型安全的检查 。
C++ 有哪些技术替代宏 ?- 常量定义 换用 const
- 函数定义 换用内联函数
3.auto关键字(C++11)
3.1 类型别名思考
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
- 类型难于拼写
- 含义不明确导致容易出错
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不能推导的场景
- auto 不能作为函数的参数以及函数的返回值
cpp
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
- auto 不能直接用来声明数组
cpp
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
- 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
- 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的使用条件
- 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 。