C++深度解析教程学习笔记(2)C++中的引用
1.C++中的引用
(1)变量名的回顾
①变量是一段实际连续存储空间的别名,程序中通过变量来申请并命名存储空间
②通过变量的名字可以使用存储空间。(变量的名字就是变量的值,&变量名是取地址操作)
(2)C++中新增加了引用的概念
①引用可以看作一个己定义变量的别名
②引用的语法:Type& name = var; //Type 为类型名,name 为引用的名字,var为己定义的变量名
③普通引用在定义时必须用同类型的变量进行初始化,函数参数引用的初始化发生在函数被调用时。
#include <stdio.h> int main()
{
//引用初探
int a = ;
int& b = a;//b为a的别名 b = ; //操作b就是操作a printf("a = %d\n", a); //
printf("b = %d\n", b); // printf("&a = %p\n", &a);
printf("&b = %p\n", &b);//&b等于&a
}
(3)引用作为变量别名而存在,因此在一些场合可以代替指针
(4)引用相对于指针来说,具有更好的可读性和实用性
2.特殊的引用——const引用
(1)const Type& name = var; //让变量拥有只读属性
(2)当使用常量对 const 引用进行初始化时,C++编译器会为这个常量值分配空间,并将引用名作为这段空间的别名。但这样用常量对 const 引用初始化将生成的是一个只读变量。
#include <stdio.h> void Example()
{
printf("Example:\n"); int a = ;
const int& b = a;//变量a的别名,const让变量b具有只读属性
int* p = (int*)&b; //&b就是变量a的地址 //b = 5;//非法,因为用const修饰的引用,其代表的变量是只读的 *p = ; //合法,修改变量a的值 printf("a = %d\n", a); //5;
printf("b = %d\n", b); //
} void Demo()
{
printf("Demo:\n"); //const引用
const int& c = ; //引用本应是变量的别名。一般,常量是不分配内存的。当编译器看
//该行时,会为常量1分配内存,并让c作为这段空间的别名。 int* p = (int*)&c; //c = 5;//非法,因为const修饰,c是只读的 *p = ;//合法,通过指针访问内存 printf("c = %d\n", c); // printf("\n", c); //5 //const修饰变量:注意与const引用的区别
const int a = ;
p = (int*)&a; //遇到&a才为常量分配内存 //a = 5;//非法,因为const修饰,a是只读的 *p = ;//合法,通过指针访问内存 printf("a = %d\n", a); //1,编译器看到a直接从符号表读其值(1)
printf("*p = %d\n", *p); //
} int main()
{
Example(); printf("\n"); Demo(); return ;
}
3.引用的本质
(1)引用在 C++中的内部实现是一个常量指针,因此引用所占用的空间大小与指针相同。
(2)从使用的角度,引用只是一个别名,C++为了实用性而隐藏了引用的存储空间这一细节。
①在编译过程中,编译器看到 int& a 的声明就会转换为 int* const a;
②看到使用引用时,会转为*a,如此隐藏了使用指针的事实。
#include <stdio.h> struct TRef
{
char& r;//引用的本质是常量指针,因此会分配4字节的空间,相当于char* const r;
}; int main()
{
char c = 'c';
char& rc = c;
TRef ref = { c }; printf("sizeof(char&) = %d\n", sizeof(char&));//1,char型变量别名,大小为1
printf("sizeof(rc) = %d\n", sizeof(rc)); //1,变量c的别名,大小为1 printf("sizeof(TRef) = %d\n", sizeof(TRef)); //结构体内有个引用,本质为指针,占4字节
printf("sizeof(ref.r)= %d\n", sizeof(ref.r)); //1,char型变量的别名,大小为1 return ;
}
引用的存储空间
#include <stdio.h> struct TRef
{
char* before; //4字节
char& ref;//4字节,本质是常量指针,会分配4字节的空间,相当于char* const ref;
char* after;//4字节 }; int main()
{
char a = 'a';
char& b = a;
char c = 'c'; TRef r = { &a, b, &c }; printf("sizeof(r) = %d\n", sizeof(r)); //
printf("sizeof(r.before) = %d\n", sizeof(r.before)); //
printf("sizeof(r.after) = %d\n", sizeof(r.after)); //
printf("&r.before = %p\n", &r.before);
printf("&r.after = %p\n", &r.after); //after和before相差8个字节,中间隔了个b引用所占用的空间 return ;
}
4.引用的意义
(1)功能性:引用在大多数情况下代替指针,可以满足需要使用指针的场合
(2)安全性:可以避开由于指针操作不当而带来的内存错误
(3)操作性:简单易用,又不失功能强大
函数返回引用
#include <stdio.h> int& demo()
{
int d = ; printf("demo: d = %d\n", d); //输出0 return d;//返回局部变量的引用,危险
} int& func()
{
static int s = ;
printf("func: s = %d\n", s);//输出0 return s; //合法,返回的是静态局部变量(位于全局存储区中的)
} int main()
{
int& rd = demo();
int& rs = func(); printf("\n");
printf("main: rd = %d\n", rd);//垃圾数值
printf("main: rs = %d\n", rs);//0,返回全局存储区中的s
printf("\n"); return ;
}
引用作为变量别名而存在,旨在代替指针,引用在编译器内部使用常量指针实现,其最终本质为指针,引用可以尽可能的避开内存错误。
5.关于const的疑问
(1)const 常量的判别准则
①只有用字面量初始化的 const 常量才会进入符号表,如 const int i = 1;
②使用其它变量初始化的 const 常量仍然是只读变量。如 const int a = b;//a 为只读变量
③被 volatile 修饰的 const 常量不会进入符号表,如 volatile const int i = 1;//这时会为 i 分配内存,且 i 每次都是从内存中取值。加 const 只是说明 i 不能作为左值。
▲在编译期间不能直接确定初始值的 const 标识符,都被作为只读变量处理。
(2)const 引用的类型与初始化变量的类型
①当用变量来初始化与 const 引用时,如果两者类型相同,则初始化变量成为只读变量。
②当用变量来初始化与 const 引用时,如果两者类型不同,则将生成一个新的变量,即引用的是另一个新变量,而不是原来的用来初始化引用的那个变量。
#include <stdio.h> int main()
{
//实验1:初始化变量的类型与引用类型相同时
const int x = ;
const int& rx = x;//x与rx的类型相同,所以rx为只读变量,不能作为左值
//但本质上还是变量,引用x的内存值(而不是符号表中的值) int& nrx = const_cast<int&>(rx);//转为普通引用 nrx = ; //nrx与rx都引用了x的内存值 printf("x = %d\n", x); //1,从符号表中读取
printf("rx = %d\n", rx); //5,从内存中读取
printf("nrx = %d\n", nrx); //5,从内存中读取 printf("&x = %d\n", &x);
printf("&rx = %d\n",&rx);
printf("&nrx = %d\n",&nrx); //x、rx、nrx地址相同 //实验2:初始化变量的类型与引用类型不同时
char c = 'c';
char& rc =c;
const int& trc = c;//c与trc类型不一致,则会生成一个新的变量,然后trc引用这个新的变量
rc = 'a'; printf("c = %c\n", c); //c
printf("rc = %c\n",rc); //c
printf("trc = %c\n",trc);//a printf("&c = %p\n", &c);
printf("&rc = %p\n",&rc); //rc与c的地址相同
printf("&trc = %p\n",&trc);//trc是一个新的地址 //实验3:volatlie
volatile const int y = ; //volatile修饰,分为y分配内存
int* p = const_cast<int*>(&y);//因y被const修饰,不能作为左值 *p = ; //因y不能作为左值,用来代替y = 6;这样的写法 printf("y = %d\n", y);//6,volatile指示y得从内存中读取
printf("p = %p\n", p);//y的地址 const int z = y; //用变量初始化const常量,z不会进入符号表,z分配内存 p = const_cast<int*>(&z);
*p = ; printf("z = %d\n", z);//7,因z没进入符号表
printf("p = %p\n", p);//z的地址 return ;
}
6.关于引用的疑问
(1)指针与引用的不同
指针 |
引用 |
|
初始化 |
值是一个内存地址,不需要初始化 |
必须在定义时初始化,之后无法代表其它变量 |
访问内存 |
通过指针可以访问对应内存地址中的值 |
对引用的操作(赋值,取地址等)都会传递到其代表的变量上。 |
const修饰 |
被const修饰成常量或只读变量。 如const int* p;//p |
const引用,表示其代表的变量具有只读属性。如,const int& a等价于const int* const a; |
(2)从使用 C++语言的角度来看,引用与指针没有任何关系。引用是变量的新名字,操作引用就是操作对应的变量。当进行 C++编程时,直接站在使用的角度看待引用,与指针毫无关系,引用就是变量的别名。
(3)从 C++编译器的角度来看,在编译器内部,使用指针常量来实现“引用”。因此,“引用”在定义时必须初始化。当对 C++代码进行调试分析时,一些特殊情况,可以考虑站在 C++编译器的角度看待引用。
引用典型问题分析
#include <stdio.h> int a = ; struct SV
{
int& x;
int& y;
int& z;
}; int main()
{
int b = ;
int* pc = new int();
//将sv各成员初始化为变量a,b,*pc等内存的引用
SV sv = {a, b, *pc};
printf("&sv.x = %p\n", &sv.x);//变量a的地址,全局区
printf("&sv.y = %p\n", &sv.y); //变量b的地址,栈
printf("&sv.z = %p\n",&sv.z); //new出来的地址,堆 //在C++中没有“引用数组”的概念,请看如下分析
//对于数组而言,其内存是连续分布的。当进行&array[1] - &array[0]
//表示前后两个元素的地址相差的值,应等于sizeof(元素的类型)。
//但如果允许定义“引用数组”的话,如下面语句,&array[1]表示第1个元素
//的地址,即元素b的地址(&b),而&array[0]表示&a,显然这两个地址是不连续的。
//所以int& array[]={a, b, *pc};//这样的定义是错误的,C++里不支持“引用数组” return ;
}
指针是一个变量,而引用是一个变量的新名字。const 引用能够生成新的只读变量,编译时不能直接确定初始值的 const 标识符都是只读变量。
C++深度解析教程学习笔记(2)C++中的引用的更多相关文章
- C++深度解析教程学习笔记(6)对象的构造和销毁
1. 对象的初始化 (1)从程序设计的角度看,对象只是变量,因此: ①在栈上创建对象时,成员变量初始化为随机值 ②在堆上创建对象时,成员变量初始化为随机值 ③在静态存储区创建对象时,成员变量初始化为 ...
- C++深度解析教程学习笔记(5)面向对象
1. 面向对象基本概念 (1)面向对象的意义在于 ①将日常生活中习惯的思维方式引入程序设计中 ②将需求中的概念直观的映射到解决方案中 ③以模块为中心构建可复用的软件系统 ④提高软件产品的可维护性和可扩 ...
- C++深度解析教程学习笔记(4)C++中的新成员
1. 动态内存分配 (1)C++通过 new 关键字进行动态内存申请,是以类型为单位来申请空间大小的 (2)delete 关键字用于内存释放 ▲注意释放数组时要加[],否则只释放这个数组中的第 1 个 ...
- C++深度解析教程学习笔记(3)函数的扩展
1.内联函数 1.1.常量与宏的回顾 (1)C++中的 const 常量可以替代宏常数定义,如: ; //等价于 #define A 3 (2)C++中是否有解决方案,可以用来替代宏代码片段呢? 1. ...
- C++深度解析教程学习笔记(1)C到C++的升级
1.现代软件产品架构图 比如商场收银系统 2.C 到 C++ 的升级 2.1变量的定义 C++中所有的变量都可以在需要使用时再定义,而 C 语言中的变量都必须在作用域开始位置定义. 2.2 regis ...
- 《Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实现
pring框架是一个分层架构,它包含一系列的功能要素,并被分为大约20个模块,如下图所示 这些模块被总结为以下几个部分: Core Container Core Container(核心容器)包含有C ...
- 《C语言深度剖析》学习笔记----C语言中的符号
本节主要讲C语言中的各种符号,包括注释符.单引号双信号以及逻辑运算符等. 一.注释符 注释符号和注释在程序的预编译期就已经被解决了,在预编译期间,编译器会将注释符号和注释符号之间的部分简单的替换成为空 ...
- Webpack新手入门教程(学习笔记)
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; text-align: center; font: 30.0px Helvetica; color: #000000 } ...
- 尚硅谷韩顺平Linux教程学习笔记
目录 尚硅谷韩顺平Linux教程学习笔记 写在前面 虚拟机 Linux目录结构 远程登录Linux系统 vi和vim编辑器 关机.重启和用户登录注销 用户管理 实用指令 组管理和权限管理 定时任务调度 ...
随机推荐
- Java执行过程
Java的运行原理 在Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器.这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口.编译程序只需要面向虚拟机,生成虚拟机能 ...
- Hbase- Hbase客户端读写数据时的路由流程
1.客户端先到zookeeper查找hbase:meta所在的RegionServer服务器 2.去hbase:meta表查找自己所要的数据所在的region server 3.去目标region s ...
- review34
Thread类与线程的创建 让线程启动时使用我们自己创建run()的两种方式:一种是继承Thread类,实现其中的run()方法,然后用继承的类用无参构造方法创建对象就可以了.第二种是实现Runnab ...
- spring: @Pointcut给重复的注解/切点定义表达式
代码如下: package ch2.test; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.a ...
- Educational Codeforces Round 33 (Rated for Div. 2)A-F
总的来说这套题还是很不错的,让我对主席树有了更深的了解 A:水题,模拟即可 #include<bits/stdc++.h> #define fi first #define se seco ...
- ipconfig | find /i "ipv4"
C:\Users\Bob>ipconfig|find /i "IPv" 本地链接 IPv6 地址. . . . . . . . : fe80::d495:6e3:6368 ...
- HDU 2203 kmp
http://acm.hdu.edu.cn/showproblem.php?pid=2203 亲和串 Time Limit: 3000/1000 MS (Java/Others) Memory ...
- IntelliJ IDEA创建Maven+SSM+Tomcat+Git项目【全程详解】
记录一下整个创建项目的过程,其中包括: Maven 项目创建: SSM配置文件: Tomcat配置: Git配置: Git忽略文件Ignore配置: 图文讲解,通俗易懂,易上手. 一.创建Maven ...
- 18 Python 模块引入
Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句. 模块让你能够有逻辑地组织你的 Python 代码段. 把相关的代码 ...
- uva11997 K Smallest Sums&&UVALive 3135 Argus(优先队列,多路归并)
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #inc ...