主页
文章
分类
系列
标签
简历
【C++ Primer(edition 5) 02】复合类型
发布于: 2021-9-6   更新于: 2021-9-6   收录于: Cpp
文章字数: 355   阅读时间: 2 分钟   阅读量:

一、引用

1、左值引用

1.1、引用的定义

引用是一个对象的别名,引用类型引用(refer to)另外一种类型。如int &refVal = val;引用的类型要和与之绑定的对象(对象)严格匹配。而且,引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起

  • 引用必须初始化。
  • 引用和它的初始值是绑定在一起的,而不是拷贝。一旦定义就不能更改绑定为其他的对象

1.2、引用规则的两种例外情况

第一个例外 在初始化常量引用时允许用任意表达式作为初始值,只要表达式的结果能够转换为引用的类型即可,允许一个常量引用绑定非常量的对象,字面值,甚至一个表达式 ^d7c880

1
2
3
4
5
int i = -42; 				
const int &rl = i;	//允许将 const int& 绑定到一个普通int对象上
const int &r2 = 42;	//正确:rl是一个常量引用
const int &r3 = rl*2;	//正确:r3是一个常量引用
int &r4 = rl*2;	//	错误:r4是一个普通的非常量引用
1
2
double dval = 3.14; 
const int &ri = dval;

理解这种例外的核心就是搞清楚当一个常量引用被绑定到另外一种类型上发生了什么。

1
2
3

const int temp = dval; //由双精度浮点数生成一个临时的整型常量 
const int &ri = temp;  //让ri绑定这个临时量

在这种情况下,ri绑定了一个临时量(temporary)对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。c++程序员们常常把临时量对象简称为临时量。

第二个例外 存在继承关系的类是一个例外,可以将基类的指针或者引用绑定到派生类对象上 可以将基类的指针或引用绑定到派生类对象上有一层极为至要的含义:当使用基类的 引用(或指针)时,实际我们并不清楚该引用(或指针)所绑定对象的真实类型。该对象可能是基类的对象,也可能是派生类的对象。 ^9b1798

1.3 sizeof()和引用

sizeof()取的是引用变量的类型大小

3、为什么要有引用

避免无休止的拷贝,极大提高了性能

二、指针

1、定义

指针是一种 “指向(point to)“另外一种类型的复合类型。也可以说是指向内存单元的特殊变量。指针的类型与所指向的对象类型必须一致

1.1 声明指针

int *ip1;从右向左读有助于阅读ip1是指向int类型的指针。

1.2 取变量地址

指针存放某个对象的地址,获取对象的地址: int i=42; int *p = &i;&取地址符

1.3 利用指针访问对象

使用解引用符(*)来访问对象 指针访问对象: cout << *p;

1.4 sizeof()和指针

sizeof()用于普通变量是指变量类型的内存大小,即编译器为变量预留的内存空间大小。 但是,当用于指针变量时,取决于编译器和操作系统存储地址所需的内存空间,与指向什么类型的变量空间是没有关系的。

2、指针操作

2.1 递增和递减

如果对指针执行递增或递减运算,编译器将认为要指向内存块中相邻的值(并假定这个值的 类型与前一个值相同),而不是相邻的字节(除非值的长度刚好是1字节,如char)。 将指针递增或递减的结果 将指针递增或递减时,其包含的地址将增加或减少指向的数据类型的sizeof (并不一定是1字节)。这样,编译器将琉保指针不会指向数据的中间或末尾,而只会指向数据的开头。 如果声明了如下指针:

1
Type* pType = Address;

则执行++piype后,pType 将包含(指向)Address + sizeoffiype

2.2 将关键字const用于指针

![[变量和基本类型#3、指针和const|指针和const]]

2.3 将指针传递给函数

3、指针定义的两种例外情况

指针的类型与所指向的对象类型必须一致这个规则有两种例外 第一种 允许令一个指向常量的指针指向一个非常量对象 ^413a8b

1
2
3
const double *cptr ; 
double dval = 3.14 ;
cptr = &dval ;

和常量引用一样,指向常量的指针也没有规定其所指的对象必须是个常量。所谓的指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过 其他途径改变。 可以这样想,所谓的常量引用和常量指针,不过是指针和引用的“自以为是”,他们觉得自己指向了常量,所以自觉的不去改变所指向的对象的值罢了。

第二种 [[复合类型#^9b1798|继承关系中存在例外]]

4、指针的值的四种状态

1.指向一个对象; 2.指向紧邻对象的下一个位置; 3.空指针; 4.无效指针。

对无效指针的操作均会引发错误,第二种和第三种虽为有效的,但理论上是不被允许的

5、空指针

空指针不指向任何对象。使用int *p=nullptr;来使用空指针。

6、空类型指针

  • void*指针可以存放任意对象的地址。因无类型,仅操作内存空间,对所存对象无法访问。

利用void*指针能做的事儿比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋给另外一个void*指针。不能直接操作void*指针所指的对象,因为我们并不知道 这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。

7、使用指针时常犯的错误

7.1 野指针

“野指针”则是不确定其具体指向的指针。“野指针”最常来自于未初始化的指针

1
2
void *p;
// 此时 p 是“野指针”

因为“野指针”可能指向任意内存段,因此它可能会损坏正常的数据,也有可能引发其他未知错误,所以C语言中的“野指针”危害性甚至比“悬空指针”还要严重。在实际的C语言程序开发中,定义指针时,一般都要尽量避免“野指针”的出现(赋初值):

1
void *p = NULL;

7.2 内存泄漏

这可能是C++应用程序最常见的问题之一:运行时间越长,占用的内存越多,系统越慢。如果在使用new动态分配的内存不再需要后,程序员没有使用配套的delete释放,通常就会出现这种情况。

7.3 指向无效的内存单元

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream> 
using namespace std;
int main()
{
	// uninitialized pointer (bad) 
	int* pTemperature;
	cout << "Is it sunny (y/n)?" << endl;
	char Userinput = 'y';
	cin >> Userinput;
	if (Userinput == 'y')
	{
		pTemperature = new int;
		*pTemperature = 30;
	}
	// pTemperature contains invalid value if user entered 'n'
	cout « "Temperature is: ' « *pTemperature;
	// delete also being invoked for those cases new wasn't done 
	delete pTemperature;
	return 0;
}

7.4 悬空指针

指针可以指向一块内存,如果这块内存稍后被操作系统回收(被释放),但是指针仍然指向这块内存,那么,此时该指针就是“悬空指针”

三、指针与引用的不同

本质上: 引用本质是一个指针

使用上: 指针与引用类似,都实现了对其他对象的间接访问。 然而指针与引用相比也有很多不同点。

其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在==指针的生命周期内它可以先后指向几个不同的对象==,但是引用一旦初始化后就不可以再改变。

其二,==指针无须在定义时赋初值==。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有个不确定的值。

其三,作为参数传递时,指针是==拷贝==同样的地址,如果函数作用域中改变指针变量的地址,则不再影响实参。引用参数实质上==传递的是实参本身==,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。

四、理解复合类型的声明

变量的定义包括一个基本数据类型(base type)一组声明符。在同一条定义语句中,虽然基本数据类型只有一个,但是声明符的形式却可以不同。也就是说, 条定义语句可能定义出不同类型的变量:

1
int i = 1024, *p = &i , &r = i;

甚至可以有多个声明符,组合出更为复杂的变量

指向指针的指针

1
int **p;

指向指针的引用 要理解变量到底是什么,最简单的方法就是==从右向左==阅读r的定义,距离变量名最近的符号对变量的类型有直接的影响

1
2
3
4
5
6
int i = 42;
int *p;
int *&r = p;   //r是一个引用变量,引用一个指向int型变量的指针变量

r = &i;
*r=0;