目录
❀❀❀没有坚持的努力,本质上并没有多大的意义。
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. 常量定义 换用 const2. 函数定义 换用内联函数
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
。