C++对C语言的拓展(1)—— 引用
1、变量名
变量名实质上是一段连续存储空间的别名,是一个标号(门牌号);
通过变量来申请并命名内存空间;
通过变量的名字可以使用内存空间。
2、引用的概念
变量名,本身是一段内存的引用,即别名(alias)。引用可以看作一个已定义变量的别名。
引用的语法:Type & name = var;
用法如下:
#include <iostream>
using namespace std; int main(void)
{
int a = ;//C编译器分配4个字节内存,a内存空间的别名
int &b = a;//b就是a的别名 a = ;
{
int *p = &a;
*p = ;
cout << a << endl;//
}
b = ;
cout << "a=" << a << ",b=" << b << endl;//a=14,b=14
return ;
}
3、规则
(1)引用没用定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故类型与原类型保持一致,且不分配内存,与被引用的变量有相同的地址。
(2)声明的时候必须初始化,一经声明,不可更改。
(3)可对引用,再次引用,多次引用的结果是某一变量具有多个别名。
(4)&符号前有数据类型时是引用,其它皆为地址。
#include <iostream>
using namespace std; int main(void)
{
int a,b;
int &r = a;
int &r = b;//error,不可更改原有的引用关系——规则(2)
float &rr = b;//error,引用类型不匹配——规则(1)
cout <<"&r="<< &r << ",&a=" << &a << endl;//变量与引用具有相同的地址——规则(1)
int &ra = r;//可对引用再次引用,表示a变量有两个别名,分别是r和ra——规则(3) return ;
}
4、引用作为函数参数
普通引用在声明时必须用其它的变量进行初始化,引用作为函数参数声明时不进行初始化。
#include <iostream>
using namespace std; struct Teacher
{
char name[];
int age;
};
void printfT(Teacher *pT)
{
cout << pT->age << endl;
} void printfT2(Teacher &pT)//pT是t1的别名,相当于修改了t1
{
pT.age = ;
cout << pT.age << endl;
} void printfT3(Teacher pT)//pT和t1是两个不同的变量
{
cout << pT.age << endl;
pT.age = ;//只会修改pT变量,不会修改t1变量
} int main(void)
{
Teacher t1;
t1.age = ; printfT(&t1);// printfT2(t1);//33,pT是t1的别名
printf("t1.age:%d\n", t1.age);// printfT3(t1);//33,pT是形参,t1拷贝一份数据给pT
printf("t1.age:%d\n", t1.age);// return ;
}
5、引用的意义
(1)引用作为其它变量的别名而存在,因此在一些场合可以代替指针;
(2)引用相对于指针来说具有更好的可读性和实用性。
c++中引入引用后,可以用引用解决的问题避免用指针来解决。
#include <iostream>
using namespace std; struct student
{
int age;
char name[];
};
void swap1(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
} void swap2(int &a, int &b)
{
int temp;
temp = a;
a = b;
b = temp;
} void printS1(struct student s)//子拷贝方式:student s=s1;结构体整个值拷贝的动作
{
cout << s.age << " " << s.name << endl;
} void printS2(struct student *sp)//指针方式
{
cout << sp->age << " " << sp->name << endl;
} void printS3(struct student &sp)//引用方式:student &s=s1;
{
cout << sp.age << " " << sp.name << endl;
} int main(void)
{
int a = ;
int b = ;
swap1(&a, &b);
cout << "a=" << a << ",b=" << b << endl;//a=20,b=10 int c = ;
int d = ;
swap2(c, d);
cout << "c=" << c << ",d=" << d << endl;//c=200,d=100 student s1 = { ,"zhang3" };
printS1(s1);//10 zhang3
printS2(&s1);//10 zhang3
printS3(s1);//10 zhang3
return ;
}
6、引用的本质
引用做参数传递时,编译器会替我们将实参取地址给引用,即:int &a = main :: &b;//a是b的引用;
当对引用进行操作赋值时,编译器帮我们隐藏*操作,即:cout<<a其实是cout<<*a;*被编译器隐去了。
思考一:C++编译器定义引用时,背后做了什么工作?
引用所占大小与指针相同;常量要初始化,引用也要初始化,引用可能是一个常量;综上两点,引用可能是一个常指针。
- 当我们去研究引用的时候,可以将引用当作一个常指针去研究;
- 当使用引用编程时,就把引用理解为变量的别名就可以了。
思考二:普通引用有自己的空间吗?
(1)引用在C++中的内部实现是一个常指针:Type & name <===> Type* const name;
(2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同;
(3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间,这是C++为了实用性而做出的细节隐藏。
间接赋值的三个必要条件:
- 定义两个变量(一个实参,一个形参);
- 建立关联,实参取地址传给形参;
- *p形参去间接的修改实参的值。
引用在实现上,只不过是把间接赋值成立的三个必要条件的后两个合二为一了。
当实参传给形参引用的时候,只不过是C++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)。
7、引用作为函数的返回值(引用当左值)
(1)当函数返回值为引用时,若返回栈变量,不能成为其它引用的初始值(不能作为左值使用);
#include <iostream>
using namespace std; int get1()
{
int a;
a = ;
return a;
} int& get2()//返回值为引用
{
int a;
a = ;
return a;
} int main(void)
{
int a1 = ;
int a2 = ; a1 = get1();//值拷贝
a2 = get2();//将一个引用赋给一个变量,会有拷贝操作,可以理解为:编译器类似做了如下隐藏操作,a2=*(get2())
int &a3 = get2();//将一个引用赋给另一个引用作为初始值,由于是栈的引用,内存非法 cout << a1 << endl;//
cout << a2 << endl;//
cout << a3 << endl;//
cout << a3 << endl;//不一定为10 return ;
}
(2)当函数返回值为引用时,若返回静态变量或全局变量,可以成为其它引用的初始值(可作为右值使用,也可作为左值使用)
#include <iostream>
using namespace std; int get1()
{
static int a;
a = ;
return a;
} int& get2()//返回值为引用
{
static int a;
a = ;
return a;
} int main(void)
{
int a1 = ;
int a2 = ; a1 = get1();//值拷贝
a2 = get2();//将一个引用赋给一个变量,会有拷贝操作,可以理解为:编译器类似做了如下隐藏操作,a2=*(get2())
int &a3 = get2();//将一个引用赋给另一个引用作为初始值,由于是静态区域,内存合法 cout << a1 << endl;//
cout << a2 << endl;//
cout << a3 << endl;//
cout << a3 << endl;// return ;
}
(3)引用作为函数返回值,如果返回值为引用可以当左值,如果返回值为普通变量不可以当左值。
#include <iostream>
using namespace std; int get1()//返回值为普通变量,函数当左值,返回的是变量的值
{
static int a=;
return a;
} int& get2()//返回值为引用,返回的是变量本身
{
static int a = ;
return a;
} int main(void)
{
int c1 = get1();//函数当右值
cout << "c1=" << c1 << endl; int c2 = get2();//函数返回值是一个引用,并且当右值
cout << "c2=" << c2 << endl; //get1()=100;//error,函数当左值
get2() = ;//函数返回值是一个引用,并且当左值 c2 = get2();
cout << "c2=" << c2 << endl; return ;
}
8、指针引用
指针是一个存放地址的变量,而指针引用指的是这个变量的引用,即对指针的引用,众所周知C++中如果参数不是引用的话会调用参数对象的拷贝构造函数,所以如果有需求想改变指针所指的对象(换句话说,就是要改变指针里面存的地址),就要使用指针引用。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std; struct teacher
{
int id;
char name[];
}; int get_mem(struct teacher** tpp)
{
struct teacher *tp = NULL;
tp = (struct teacher*)malloc(sizeof(struct teacher));
if (tp == NULL)
{
return -;
}
tp->id = ;
strcpy(tp->name, "li4"); *tpp = tp;//tpp是实参的地址,*实参的地址去间接的修改实参的值
return ;
} void free_teacher(struct teacher **tpp)
{
if(tpp==NULL)
{
return ;
}
struct teacher *tp = *tpp;
if (tpp != NULL)
{
free(tp);
*tpp = NULL;
}
} int get_mem2(struct teacher* &tp)//指针的引用做函数参数
{
tp = (struct teacher*)malloc(sizeof(struct teacher));//给tp赋值,相当于给main函数中的tp赋值
if (tp == NULL)
{
return -;
}
tp->id = ;
strcpy(tp->name, "wang5");
return ;
} void free_teacher2(struct teacher *&tp)
{
if (tp != NULL)
{
free(tp);
tp = NULL;
}
} int main(void)
{
struct teacher *tp = NULL;
get_mem(&tp);//C语言中的二级指针
cout << "id=" << tp->id << ",name=" << tp->name << endl;//id=100,name=li4
free_teacher(&tp); get_mem2(tp);//指针的引用
cout << "id=" << tp->id << ",name=" << tp->name << endl;//id=300,name=wang5
free_teacher2(tp);
return ;
}
9、const引用
const引用可以防止对象的值被随意修改。
(1)const对象的引用必须是const的,将普通引用绑定到const对象是不合法的,原因:既然对象是const的,表示不能被修改,引用当然也不能修改,必须使用const引用。
const int a=; int &b=a;//这种写法是不合法的,编译不过。
(2)const引用可使用相关类型的对象(常量,非同类型的变量或表达式) 初始化。这个是const引用与普通引用最大的区别。
即:
const int &a=;//是合法的; double a=3.14; const int &b=a;//也是合法的
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std; int main(void)
{
//普通引用
int a = ;
int &b = a;
cout << "b=" << b << endl;//b=10 //const引用
const int c = ;
const int &d = c;//如果想对一个常量进行引用,必须是一个const引用
cout << "c=" << c << endl;//x=20
cout << "d=" << d << endl;//y=20 //const引用
int x = ;
const int &y = x;//相反,如果一个普通变量,用一个const引用接收是可以的
cout << "x=" << x << endl;//x=100
cout << "y=" << y << endl;//y=100 x = ;
//y = 22;//error,常引用限制为只读,不能通过y去修改x的值
cout << "x=" << x << endl;//x=21
cout << "y=" << y << endl;//y=21 return ;
}
10、const引用的原理
const引用的目的是,禁止通过修改引用值来改变被引用的对象。const引用的 初始化特性较为微妙,可通过如下代码说明:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std; int main(void)
{
double val = 3.14;
const int &ref = val;
double &ref2 = val;
cout << "ref=" << ref<<",ref2="<<ref2 << endl;//ref=3,ref2=3.14 val = 4.14;
cout << "ref=" << ref << ",ref2=" << ref2 << endl;//ref=3,ref2=4.14 return ;
}
上述输出结果为 ref=3,ref2=3.14 和 ref=3,ref2=4.14。因为 ref 是 const 的,在初始化的过程中已经给定值,不允许修改。而被引用的对象是 val,是非 const 的,所以 val的修改并未影响ref的值,而 ref2 的值发生了相应的改变。
那么,为什么非 const 的引用不能使用相关类型初始化呢?实际上,const引用 使用相关类型对象初始化时发生了如下过程:
int temp = val;
const int &ref = temp;
如果 ref 不是 const 的,那么改变 ref 值,修改的是 temp,而不是 val。期望对 ref 的赋值会修改 val 的程序员会发现 val 实际并未修改。
结论:
1)const int & e 相当于 const int * const e
2)普通引用 相当于 int *const e
3)当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名
4)使用字面量对const引用初始化后,将生成一个只读变量
C++对C语言的拓展(1)—— 引用的更多相关文章
- go语言---传值和传引用
go语言---传值和传引用 https://blog.csdn.net/cyk2396/article/details/78893828 1.定义: b = a; b.modify(); 如果b的修改 ...
- Microsoft Translator:打破语言障碍 拓展全球沟通新机遇
Translator:打破语言障碍 拓展全球沟通新机遇"> 作者:Olivier Fontana, 微软研究院Microsoft Translator产品战略总监 世界越来越小,全球协 ...
- go语言包与包引用
go语言中包(package)与java中的包(package)非常类似,都是组织代码的方式,而且都和磁盘上的目录结构存在对应关系. go语言中,包名一般为go代码所在的目录名,但是与java不同的是 ...
- Lambda语言篇 —— lambda, 方法引用, 目标类型和默认方法
本文介绍了Java SE 8中新引入的lambda语言特性以及这些特性背后的设计思想.这些特性包括: lambda表达式(又被成为"闭包"或"匿名方法") 方法 ...
- Microsoft Translator:打破语言障碍 拓展全球沟通新机遇
作者:Olivier Fontana, 微软研究院Microsoft Translator产品战略总监 世界越来越小,全球协作.共同创新已经成为常态.在微软研究院,我们对此尤为感同身受——从北京到雷德 ...
- C++ 基础 2:C++ 对 C 语言的拓展
1 引用 1.1 定义及编程实践 引用,是某个已存在变量的另一个名字. 一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量. 注意: 引用没有定义,是一种关系型声明.声明它和原有某一 ...
- c语言模拟实现oc引用计数
#include<stdio.h> #include<stdlib.h> //在c中引入 引用计数机制 // 要解决的问题: 1,指向某块动态内存的指针有几个? // ...
- C语言中 指针、引用和取值
指针是一个存储计算机内存地址的变量.从指针指向的内存读取数据称作指针的取值.指针可以指向某些具体类型的变量地址,例如int.long和double.指针也可以是void类型.NULL指针和未初始化指针 ...
- C语言-数据的快速引用
1.常量:程序运行中,不会改变 整形常量 实形常量 字符常量:使用单引号引起的单个字符或者转移字符 ‘a’ 字符串常量:使用双引号引起的单个或者多个字符序列 "ab",存储的时候, ...
随机推荐
- Ajax+Spring MVC实现跨域请求(JSONP)
背景: AJAX向后台(springmvc)发送请求,报错:已阻止交叉源请求:同源策略不允许读取 http://127.0.0.1:8080/DevInfoWeb/getJsonp 上的远程资源.可 ...
- VM and Docker Container
https://www.zhihu.com/question/48174633 在开始讨论前,先抛出一些问题,可先别急着查看答案,讨论的过程可以让答案更有趣,问题如下: Docker 容器有自己的ke ...
- flex 实现图片播放 方案二 把临时3张图片预加载放入内存
该方案,是预加载:前一张,当前,下一张图片,一共3张图片放入内存中.这样对内存的消耗可以非常小,加载之后的图片就释放内存. 下面示例一个是类ImagePlayers,一个是index.mxml pac ...
- IEEE802.11数据帧在Linux上的抓取 80211格式转8023帧格式
转:http://blog.csdn.net/dog250/article/details/7749372 终于得到了梦寐的<802.11无线网络权威指南>,虽然是复印版本,看起来也一样舒 ...
- Python编程-多态、封装、特性
一.多态与多态性 1.多态 (1)什么是多态 多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承) 序列类型有多种形态:字符串,列表,元组. 动物有多种形态:人,狗,猪 文 ...
- 以太坊钱包Geth使用命令
一.启动以太坊钱包Geth 打开一个控制台,执行同步区块命令 #同步测试链geth --fast --cache=512 --rpc --rpcapi personal,db,eth,net,web3 ...
- start、run、join
首先得了解什么是主线程,当Java程序启动时,一个线程立刻运行,该线程通常叫做程序的主线程(main thread).主线程的重要性体现在两方面:1. 它是产生其他子线程的线程:2. 通常它必须最后完 ...
- 20145240 《Java程序设计》第六周学习总结
20145240 <Java程序设计>第六周学习总结 教材学习内容总结 InputStream与OutputStream 10.1.1串流设计的概念 Java将输入/输出抽象化为串流,数据 ...
- pylab.show()没有显示图形图像(python的matplotlib画图包)
no display name and no $DISPLAY environment variable ============================ @Neil's answer is ...
- Linux软件安装常用方法
1.软件安装卸载,分几种情况: A:RPM包,这种软件包就像windows的EXE安装文件一样,各种文件已经编译好,并打了包,哪个文件该放到哪个文件夹,都指定好了,安装非常方便,在图形界面里你只需要双 ...