指针和引用 , 指针空值nullptr

引用和指针

1. 引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间, 使用方式和普通变量相同, 当原变量来对待 。比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。

底层实现: 通过指针来实现,

2. 形式: 类型+& + 新名称 = 新值

void test(){
     
	int a = 10;
	int b = -1;
	// ra引用, 是变量a的别名, a所代表的空间有两个名字, 代表还是原变量本身
	int& ra = a;

	ra = 20;// a 和 ra 都为 20

	// 引用在定义之后就不会再改变指向了, 始终指向本体
	int& rra = a;
	rra = b; // a变为-1, 修改了值
} 

注意: 引用类型必须和引用实体是同种类型的

3. 引用特性

  1. 引用在定义时必须初始化, 可以改变内容, 但是不能改变指向
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体
void Test2()
{
     
 	int a = 10;
 	// int& ra; // 该条语句编译时会出错
	int& ra = a;
 	int& rra = a;
 	printf("%p %p %p\n", &a, &ra, &rra); 
}

引用相当于是一个const指针, 引用的底层就是通过指针来实现的, 指向的空间不能变, ==> 类型* const --> 指向在定义时确定, 不会变, 从一而终(int* const) --> const的指针
而 const 类型* const --> 表示内容和指向都不能变(const int* const a = 5;)

  1. 在指针前加const 指向不变
  2. 在类型前加const 内容不会
void test3(){
     
	// 常引用: 常引用可以指向const变量, 常量, 临时变量
	const int c = 30;
	const int& rc = c;
	//rc = 100; // const引用的内容不能变 --> 常引用
	const int& r1 = 10;

	double d = 2.3;
	ra = d; // a = 2
	double& rd = d;
	// int& rd2 = d; // 类型不一致
	const int& rd2 = d;// 发生了隐式类型转换 生成了一个int的临时变量2, 这里的rd2不是d(double)的别名, 指向的是一个临时变量, 不是d, 因为临时变量具有常性, 

	int* pa = &a;
	//const int& rpa = pa;// 当两个类型不支持隐式类型转换时 加了const也不行
}

4. 使用场景

  1. 做参数
// 交换两个数值的内容
void Swap(int* array, int i, int j){
      // Swap(array, 2, 3) i和j相当于是地址, 索引 传的是数组
	int tmp = array[i];
	array[i] = array[j];
	array[j] = tmp;
}
// 不仅传地址, 还传值, 
void swap(int* a, int* b){
     
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
// 1. 引用做参数
// 这里的a, b为局部变量, 但是指向的是外部的本体, 局部变量交换内容也就相当于本体交换了
// 底部实现其实和swap是一样的(传指针一样) , 都是先取地址, 但是和传指针不同的是引用只传地址, 没有传内容(值),所以快
void swap2(int& a, int& b){
     
	int tmp = a;
	a = b;
	b = a;
}
void test4(){
     
	int a = 10;
	int b = 20;

	swap(&a, &b);
	swap2(a, b);
}
  1. 做返回值
// 2. 引用做返回值(不能返回临时变量(不能反悔生命周期小于函数生命周期的变量)
int& Add(int& ra){
     
	return ++ra;
}
int& Add(int a, int b){
     
	int c = a + b;
	return c; // c为一局部变量, Add栈帧调用完了 之后就销毁了, 下一次再调其他的函数, ret指的就不是c了
}
void test5(){
     
	int a = 10;
	int& ra = Add(a);// 11
	ra += 10;// 21
}

注意: 如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

5 传值、传引用效率比较

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

  • 参数传引用比传值效率高, 传引用没有拷贝发生
  • 返回引用比返回值效率高, 返回引用没有拷贝发生

传值和指针在作为传参以及返回值类型上效率相差很大。相比之下, 引用比传值上效率更高,

6 引用和指针的区别

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

引用和指针的不同点:

  1. 引用在定义时必须初始化,指针没有要求
  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  3. 没有NULL引用,但有NULL指针
  4. 在sizeof中含义不同:引用结果为 引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  6. 多级指针,但是没有多级引用
  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  8. 引用比指针使用起来相对更安全
// 对引用操作就是对本体操作, 对copy操作不会影响本体
struct B{
     
	int _a;
	int _b;
	int _c;
};
void test6(){
     
	B b; // 结构体变量
	B& rb = b;// &b 和 &rb 的值是一样的
	B* pb = &b;
	B copy = b;// 结构体的副本, 克隆(双胞胎) ©值和&b不一样 另一个不同的空间

	b._a = 10;
	rb._a = 20;// 编译器在底层会翻译成指针的操作 rb->_a = 20
	pb->_a = 40;// 这三个改的都是同一个空间的内容

	// 这是另外一个新的空间
	copy._a = 30;// 只影响copy中的内容, 不会影响b的内容

	// rb->_a = 10; 不能这样写, 是错的, 只是底层实现
}

void test7(){
     
	// 指针和引用的区别, 
	// 1. 引用在定义时必须初始化, 指针可以不用初始化, 
	int a = 10;
	int& ra = a;
	int* pa;
	// 2. 只有一级引用, 没有多级引用, 指针可以有多级指针
	int& ra2 = a;
	int** pa2 = &pa;
	int*** pa3 = &pa2;
	// 3. 引用的自加是实体内容自加, 而指针自加是地址的偏移
	ra++; // a的内容加1
	pa++; // 向后偏移4个字节的空间
	// 4. sizeof(引用)表实体的大小, sizeof(指针)表示指针的大小
	B b;
	B& rb = b;
	B* pb = &b;
	cout << "引用大小:" << sizeof(rb) << endl;// 求b的大小 = 12
	cout << "指针大小:" << sizeof(pb) << endl;// 求指针的大小 = 4

	// 5. 有空指针 , 但是没有空引用
	//int& ra3 = NULL; 
	int* pc = NULL;
	// 6. 引用更加安全, 不会有空异常

}

7. 引用的价值:

  1. 使用简单(不用取地址, 不用解引用, 编译器帮我们做自动解引用)
  2. 使用安全(在定义的时候必须初始化, 就没有空引用这种说法(在底层就相当于不会产生空指针), 不会产生解引用异常的问题)
  3. 想要用const指针的地方 都可以用引用,

指针空值nullptr(C++11)

C++98中的指针空值
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。NULL实际是一个宏,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

void f(int){
     
	 cout<<"f(int)"<<endl; }
void f(int*){
     
	 cout<<"f(int*)"<<endl; }
int main(){
     
	 f(0);// f(int)
	 f(NULL); // f(int)
	 f((int*)NULL); // f(int*)
	 return 0; 
}

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。
  3. 表示指针空值时建议最好使用nullptr。

你可能感兴趣的:(C++知识点,c++)