C++进阶剖析(二)之引用的本质分析和三目运算符

1.1 三目运算符 和bool类型
1.1.1 bool类型

  • C++中的bool类型
    • C++在C的基本类型上增加了bool
    • C++ 中的bool 可取的指 只有true 和false
    • 理论上bool只占用一个字节

注意 :true 代表真值,编译器内部用1表示
注意 :false 代表假值,编译器内部用0表示

1.1.2 bool类型 实例

  • bool类型只有true(非0 ) 和false(0)两个值
  • C++编译器将非0值转换为true ,0值转换为false;
  • 实例1
    只需要记住一点:bool类型的取值就是0 或1
int main()
{
	bool b  =false;
	int a  = b; 
	printf("sizeof(b)=%d\n",sizeof(b));   //1
	printf("a =%d,b=%d\n",a,b);          //0  0
	b =10 ;
	a =b;
	printf("a =%d,b=%d\n",a,b);     // 1  1
	
	b =-5 ;
	a =b ;
	printf("a =%d,b=%d\n",a,b);   // 1  1
	a =10;
	b =a;
	printf("a =%d,b=%d\n",a,b);  // 10  1 
	return 0;
}

1.1.3 bool类型是C++中的基本数据类型

  • 可以定义bool类型的全局变量
  • 可以定义bool类型的常量
  • 可以定义bool类型的指针
  • 可以定义bool类型数组

1.1.4 三目运算符

int main()
{
	int a =1;
	int b =2;
	(a < b ? a:b) =3;   //在c++中是成立的
	printf("a= %d\n",a);   
	printf("b= %d\n",b);

	return 0;
}

1.1.5三目运算符在C语言和C++中的比较

  • 在C语言中三目运算符只能作为右值使用
  • 在C++中三目运算符既可以作为左值,又可以作为右值。

注意:三目运算符可能返回值中如果有一个是常量值,则不能作为左值使用

1.1.6 C++ 对三目运算符中升级
C++对三目运算符做了怎样的升级,这种升级的意义是什么?

  • 当三目运算符的可能返回都是变量时,返回的是变量引用
  • 当三目运算符的可能取值中有常量时,返回的是值。
int a = 1;
int b =2; 
(a<b ? a:b) = 3; //返回a或b的引用,可以作为左值
(a<b ? 1:b)=4;  //error ,返回1或b不能做左值

1.2 引用
1.2.1 问题引出
变量是一段连续空间的别名,那么变量只能有一个别名吗?
1.2.2 引用
C++中提出了引用的概念。

  • 引用可以看做已经定义了变量的别名
  • 引用语法: Type & name =var;
int  a=10;
int & b= a;
b=5; // 操作b就是操作a

注意: 普通引用定义时必须用同类型的变量进行初始化
1.2.3 引用的错误示例

//error引用1  使用不同类型的引用
int a =10;
float &b =a;

在这里插入图片描述

//error引用2 定义引用,但是不初始化
int a =10;
int &b ;

在这里插入图片描述

//error引用3 对常用进行引用 
int & b =10 ;

在这里插入图片描述

1.3引用的本质和意义
1.3.1 引用的意义
引用的意义:
(1)引用作为一个变量的别名,在一些场合可以替代指针
(2)引用提高了代码的可读性和实用性
(3)注意:引用作为函数的参数不需要初始化,在调用函数的时候才进行初始化。
注意:普通引用是对变量的引用,如果调用这个函数的时候参数是常数,就会出错。错误案例代码如下

void  swap1(int & a,int &b)
{
	int c =a;
	 a=b;
	 b =c;
}
int main()
{
	swap1(1,2) ; //error
// 不能直接将参数赋值为常数。这样就不能进行引用了。
	return 0;
}

1.3.2 特殊的引用
(1)const 对变量的引用
const 对变量引用,那么这个变量就拥有只读属性,不能直接修改变量的值,可以间接修改变量的值。
示例程序如下:

int main()
{
	int  a = 10;
	const int &b =a;
//	b =3;   //error
	int *p =(int*) &b;
	*p= 3;
	printf("a= %d\n",a) ;   //3
	printf("b= %d\n",b);	//3

	return 0;
}

(2)const 对常量的引用
const 对常量引用,const int &b =1; C++编译器会对这个常量值 分配内存,并将引用名作为这段空间的别名。
const int & b =1;
int * p =(int *) &b;
b =5; //error ,只读变量
*p =5; //ok 修改变量 a 的值

结论: 使用常量对const 引用初始化化后将生成一个只读变量。

在C++中要对一个变量定义只读属性,只需要给这个变量定义const引用。

1.3.3 引用有自己的存储空间吗?
示例代码:

struct student
{
	char & a;
	char & b;
};

int main()
{
	printf("student :%d\n",sizeof(student));   //8
	printf("sizeof(char &) :%d\n",sizeof(char&)); //1
	return 0;
}

1.3.4 引用本质
在这里插入图片描述
注意

  1. c++ 编译器在编译过程中用 指针常量作为引用的内部实现,因此引用所占的内存空间与指针相同。
  2. 从使用角度看,引用只是一个别名,c++为了实用性而隐藏了引用的存储空间这一细节
struct TRef
{
	char* before;
	char& ref;
	char* after;
};

int main(int argc, char* argv[])
{
	char a = 'a';
	char& b = a;
	char c = 'c';
	TRef r = {&a, b, &c};
	printf("sizeof(r) = %d\n", sizeof(r));                    //12
	printf("sizeof(r.before) = %d\n", sizeof(r.before));        // 4
	printf("sizeof(r.after) = %d\n", sizeof(r.after));          //4 
	printf("&r.before = %p\n", &r.before);                   //
	printf("&r.after = %p\n", &r.after);					//

	return 0;
}

在这里插入图片描述

  • 反汇编代码
    在这里插入图片描述

1.3.5 函数返回值是引用
不能返回局部变量的引用,但可以返回静态的局部变量。
注:利用引用可以避免重新改变变量的值。(在函数参数中经常用到)

1.3.6 引用 的意义
C++中的引用旨在大多数情况下代替指针

  • 功能性 : 可以满足多数需要使用指针的场合
  • 安全性 : 可以避免由于制作操作不当带来的内存错误
  • 操作性 : 简单易用,有不失功能强大
//error
int& func1()    //实际是为了返回指针 int * const ,不要返回局部变量的引用
{
	int d =0;
	printf("func1: d =%d\n",d);
	return d;
}

//ok
int& func2()
{
	static int s =0;
	printf("func2: s =%d\n",s);
	return s;
}
int main()
{
	int & md =func1();
	int & ms = func2();

	printf("\n");
	printf("main: md =%d\n",md);
	printf("main: ms =%d\n",ms);
	printf("\n");
	md =10;
	ms =11;

	func1();
	func2();
	printf("\n");
	printf("main: md =%d\n",md);
	printf("main: ms =%d\n",ms);
	printf("\n");
}

结果:
在这里插入图片描述

1.4
1.4.1const 什么时候是常量,什么时候是只读变量?

  • 只有用字面值常量初始化后的const常量才是真正意义上的常量
  • 被变量初始化后的const常量是只读的变量,不会进入符号表中
  • 被volatile修饰的const 常量也是只读变量,const在这里仅仅表示是只读的。

注意:在编译期间不能确定初始值的const常量都是只读变量
const 引用只不过是让变量拥有了只读属性
1.4.2
为了兼容C语言语法,在C++中,定义const 常量的时候,也会分配空间,只不过我们不使用这个内存空间。当遇到&的时候,才会使用这个 空间。
代码如下:

#include <stdio.h>

int main()
{
    const int x = 1;
    const int& rx = x;  //分配内存
    int& nrx = const_cast<int&>(rx);    //
    
    nrx = 5;       
    
    printf("x = %d\n", x);     		   // 1 从符号表里取值
    printf("rx = %d\n", rx);           //  5
    printf("nrx = %d\n", nrx); 		  // 5 
    printf("&x = %p\n", &x);    	  // 三个地址相同
    printf("&rx = %p\n", &rx);        //
    printf("&nrx = %p\n", &nrx);     //
   
   cout<<"volatile const int y = 2;"<<endl;
    volatile const int y = 2;
    int* p = const_cast<int*>(&y);
    *p = 6;
    
    printf("y = %d\n", y);  //  6
    printf("p = %p\n", p); //
    
    const int z = y;
    
    p = const_cast<int*>(&z);
    
    *p = 7;
    
    printf("z = %d\n", z);   //7
    printf("p = %p\n", p);
   
   cout<<"============================"<<endl;
    char c = 'c';
    char& rc = c;
    const int& trc = c;
    
    rc = 'a';
    
    printf("c = %c\n", c);                //a
    printf("rc = %c\n", rc);			// a
    printf("trc = %c\n", trc);            //c
    
    return 0;
}

1.4.3 引用和指针有什么关系?
引用和指针有什么关系,如何理解“引用的本质就是指针常量”。

  • 指针是一个变量
    (1)值为一个内存地址,不需要初始化,可以保存不同的地址
    (2)通过指针可以访问对应内存地址中的值
    (3)指针可以被const 修饰为常量或者只读变量

  • 引用只是一个变量的新名字
    (1) 对引用的操作(赋值,取地址)都会传到代表的变量上
    (2) const引用使其代表的变量具有只读属性
    (3) 引用必须在定义时初始化,之后无法代表其他变量

  • 从使用C++ 语言角度看
    (1)引用和指针没有任何区别
    (2)引用是变量的新名字,操作引用就是操作对应的变量

  • 从 C++编译器角度看
    (1)为了支持新概念“引用”必须要一个有效的解决方案
    (2)在编译器内部,使用指针常量实现引用
    (3)因此,引用在定义时候必须初始化

  • 工程开发中:
    (1)进行C++编程时,直接站在使用的角度看待引用,引用和指针毫无关系,引用就是变量的别名
    (2)对C++代码进行调试分析时,一些特殊情况,可以考虑站在C++编译器的角度看到问题。

1.4.4 c++中不支持数组的引用(重要的事情说三遍)
//c++ 天生就要支持C语言的特性,而C语言中数组是一段连续的内存空间,C++需要兼容这个特性,而数据的引用就破坏了这个特性所以C++中不存在数组的引用。

#include <stdio.h>

int a = 1;

struct SV
{
    int& x;
    int& y;
    int& z;
};
int main()
{
    int b = 2;
    int* pc = new int(3);
    SV sv = {a, b, *pc};
    int& array[] = {a, b, *pc}; // &array[1] - &array[0] = ?  Expected ==> 4       error
    
    printf("&sv.x = %p\n", &sv.x);
    printf("&sv.y = %p\n", &sv.y);
    printf("&sv.z = %p\n", &sv.z);
    
    delete pc;
    
    return 0;
}

1.5 引用的各种情况总结
1.5.1 普通引用
普通引用分为三种情况:

  • 普通对变量引用
int main(int argc, char* argv[])
{
	
	int a= 10;
	int &b =a;
	int * p = &b;
	*p =20;
	printf("a = %d\n",a);  //20
	printf("b = %d\n",b);  //20
	return 0;
}
  • 普通引用做函数参数
void swap(int &a ,int &b)
{
	int c =a;
	a= b;
	b =c;
}

int main(int argc, char* argv[])
{
	
	int a= 10;
	int b =20;
	swap(a,b);
	printf("a = %d\n",a);  //20
	printf("b = %d\n",b);  //10
	return 0;
}
  • 普通引用做返回值
#include <stdio.h>


int&  getNum()
{
	 static int a  =10;  //   这里必须是 static, 必须是具有全局作用周期的变量,如果去掉static就会报错
	 	return a;
}

int main(int argc, char* argv[])
{
	
	int  a = getNum();
	printf("a = %d\n",a);   //10
	
	return 0;
}

  • 普通引用:对指针的引用
#include <stdio.h>

#include <stdlib.h>

void test(int * &p )
{
	p =(int*)malloc(sizeof(int));
	*p =10;
}

int main(int argc, char* argv[])
{
	int * p =NULL;
	test(p);
	printf("*p = %d\n",*p);
	printf("p = %p\n",p);
	return 0;
}

结果:
*p = 10
p = 0x1423010

1.5.2 const 引用
const引用分为以下几种情况:

  • const 引用对变量引用
int main(int argc, char* argv[])
{
	
	const int a =5;
	const int & b =a;
	int * p =const_cast<int*>(&b);
	*p =10;
	printf("a = %d\n",a);  
	printf("b = %d\n",b);  
	printf("*p = %d\n",*p);  
	
	printf("&a = %p\n",&a);  
	printf("&b = %p\n",&b);  
	printf("p = %p\n", p);  

	return 0;
}

结果如下:
a = 10
b = 10
*p = 10
&a = 0x7ffc9838e2d4
&b = 0x7ffc9838e2d4
p = 0x7ffc9838e2d4

  • const 引用对常量引用
int main(int argc, char* argv[])
{
	
	const int a =5;
	const int & b =a;
	int * p =const_cast<int*>(&b);
	*p =10;
	printf("a = %d\n",a);  
	printf("b = %d\n",b);  
	printf("*p = %d\n",*p);  
	
	printf("&a = %p\n",&a);  
	printf("&b = %p\n",&b);  
	printf("p = %p\n", p);  

	return 0;
}

结果如下:
a = 5
b = 10
*p = 10
&a = 0x7ffee63bbb74
&b = 0x7ffee63bbb74
p = 0x7ffee63bbb74

  • const 引用做函数参数
void add(const int& a,const int & b)
{
	printf("a+b = %d\n", a+b);
}
// const 引用做函数参数,传值的时候可以是字面值常量
// 如果这里是普通引用,那么传入字面值常量就会报错,因为普通引用的对变量的引用。
int main(int argc, char* argv[])
{
	add(1,2);
	return 0;
}
  • const 引用做返回值

注意下面这个程序是错的,因为返回了局部变量的const引用

const int & test()
{
	int  a =10;
	return a;
}


int main(int argc, char* argv[])
{
	const int a =test();
	printf("a =%d\n",a);
	return 0;
}

1.5.3 遗忘的知识点
const 引用的类型和初始化变量的 类型

  • 相同 初始化变量为只读变量
  • 不同 形成一个新的只读变量
int main(int argc, char* argv[])
{
	char  c ='c';
	const	char &b = c;
	const int & a =c;
	printf("a =%c\n",a);
	printf("b =%c\n",b);
	printf("c =%c\n",c);
	int *p =const_cast<int*>(&a);
	*p ='A';

	printf("a =%c\n",a);
	printf("b =%c\n",b);
	printf("c =%c\n",c);

	return 0;
}

结果
a =c
b =c
c =c
a =A
b =c
c =c

1.5.4补充1
const指针的引用没有搞懂失败原因

int main()
{
	int * p = (int*)malloc(sizeof(int));
	const int *& tmp1 = p;  //error
	int *& tmp2 = p;  
	int a =10;
	const int & b =a;
	
	return 0;
}

在这里插入图片描述
1.5.5 补充2 结构体引用和const 结构体引用

  • 结构体引用
struct Teacher
{
	char name[64];
	int age;
};

void func1(Teacher &t)
{
	strcpy(t.name,"zhangsan");
	t.age = 32;
}


int main()
{
	Teacher t1;
	t1.age =20;
	strcpy(t1.name,"lili");
	printf("================before func1==========================\n");
	printf("main t1.name =%s\n",t1.name);
	printf("main t1.age=%d\n",t1.age);
	printf("================after func1==========================\n");
	func1(t1);
	printf("main t1.name =%s\n",t1.name);
	printf("main t1.age=%d\n",t1.age);
	return 0;
}

在这里插入图片描述

  • const 结构体引用
//error程序 
//不能改变只读Teacher的值
void func2(const Teacher &t)
{
	strcpy(t.name,"zhangsan");
	t.age = 32;
}

在这里插入图片描述
void func2(const Teacher &t) const引用做函数参数在后面的类和对象中经常用到

参考一: 狄泰软件学院课程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值