举例剖析C++中引用的本质及引用作函数参数的使用

引用的意义与本质
1)引用作为其它变量的别名而存在,因此在一些场合可以代替指针
2)引用相对于指针来说具有更好的可读性和实用性

引用本质思考:
思考、C++编译器背后做了什么工作?

#include <iostream>
using namespace std; 

int main()
{
  int a = 10;
  // 单独定义的引用时,必须初始化;说明很像一个常量
  int &b = a;
  // b是a的别名
  b = 11;
  cout << "b--->" << a << endl;
  printf("a:%d\n", a);
  printf("b:%d\n", b);
  printf("&a:%d\n", &a);
  printf("&b:%d\n", &b);
  system("pause");
  return 0;
}

引用是一个有地址,引用是常量。

char *const p

引用的本质:
1)引用在C++中的内部实现是一个常指针
Type& name <--> Type*const name
2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏

间接赋值成立的三个条件:
1定义两个变量(一个实参一个形参)
2建立关联实参取地址传给形参
3*p形参去间接的修改实参的值

引用在实现上,只不过是把:间接赋值成立的三个条件的后两步和二为一。
当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(常量指针)。

引用做函数参数

普通引用在声明时必须用其它的变量进行初始化,
引用作为函数参数声明时不进行初始化

//复杂数据类型的引用
#include <iostream>
using namespace std; 

struct Teacher
{
  char name[64];
  int age;
}; 

void printfT(Teacher *pT)
{
  cout << pT->age << endl;
} 

//pT是t1的别名 ,相当于修改了t1
void printfT2(Teacher &pT)
{
  //cout<<pT.age<<endl;
  pT.age = 33;
} 

//pT和t1的是两个不同的变量
void printfT3(Teacher pT)
{
  cout << pT.age << endl;
  pT.age = 45; //只会修改pT变量 ,不会修改t1变量
}
void main()
{
  Teacher t1;
  t1.age = 35; 

  printfT(&t1); 

  printfT2(t1); //pT是t1的别名
  printf("t1.age:%d \n", t1.age); //33 

  printfT3(t1);// pT是形参 ,t1 copy一份数据 给pT   //---> pT = t1
  printf("t1.age:%d \n", t1.age); //35 

  cout << "hello..." << endl;
  system("pause");
  return;
}

引用的难点:函数返回值是引用(引用当左值)
当函数返回值为引用时,若返回栈变量,不能成为其它引用的初始值,不能作为左值使用;
若返回静态变量或全局变量,可以成为其他引用的初始值,即可作为右值使用,也可作为左值使用。
C++链式编程中,经常用到引用。

#include <iostream>
using namespace std;
//返回值是基础类型,当引用
int getAA1()
{
  int a;
  a = 10;
  return a;
} 

//基础类型a返回的时候,也会有一个副本
int& getAA2()
{
  int a; // 如果返回栈上的引用,有可能会有问题
  a = 10;
  return a;
} 

int* getAA3()
{
  int a;
  a = 10;
  return &a;
} 

int main()
{
  int a1 = 0;
  int a2 = 0; 

  a1 = getAA1();
  a2 = getAA2(); // a是10
  int &a3 = getAA2(); // 若返回栈变量,不能成为其他引用的初始值
  cout << a1 << endl;
  cout << a2 << endl;
  cout << a3 << endl; // a3是乱码,这里出现了问题
  // 编译器看到a3是个引用,自动进行对a3的地址进行取值
  // 但是函数getAA2退出的时候已经释放了这个地址的内存,所以这里是乱码 

  return 0;
}

返回值是static变量,当引用

//static修饰变量的时候,变量是一个状态变量
int j()
{
  static int a = 10;
  a++;
  printf("a:%d \n", a);
  return a; 

} 

int& j1()
{
  static int a = 10;
  a++;
  printf("a:%d \n", a);
  return a;
} 

int *j2()
{
  static int a = 10;
  a++;
  printf("a:%d \n", a);
  return &a;
} 

void main()
{
  // j()的运算结果是一个数值,没有内存地址,不能当左值
  //11 = 100;
  //*(a>b?&a:&b) = 111;
  //当被调用的函数当左值的时候,必须返回一个引用
  j1() = 100; //编译器帮我们打造了环境
  j1();
  *(j2()) = 200; //相当于手工的打造,做左值的条件
  j2();
  system("pause");
}

返回值是形参,当引用

int g1(int *p)
{
  *p = 100;
  return *p;
} 

int& g2(int *p) //
{
  *p = 100;
  return *p;
} 

//当使用引用语法的时候 ,不去关心编译器引用是怎么做的
//当分析乱码这种现象的时候,才去考虑c++编译器是怎么做的。。。。
void main()
{
  int a1 = 10;
  a1 = g2(&a1); 

  int &a2 = g2(&a1); //用引用去接受函数的返回值,是不是乱码,关键是看返回的内存空间是不是被编译器回收了。。。。
  printf("a1:%d \n", a1);
  printf("a2:%d \n", a2); 

  system("pause");
}
时间: 2016-03-11

C++浅拷贝与深拷贝及引用计数分析

C++浅拷贝与深拷贝及引用计数分析 在C++开发中,经常遇到的一个问题就是与指针相关的内存管理问题,稍有不慎,就会造成内存泄露.内存破坏等严重的问题.不像Java一样,没有指针这个概念,所以也就不必担心与指针相关的一系列问题,但C++不同,从C语言沿袭下来的指针是其一大特点,我们常常要使用new/delete来动态管理内存,那么问题来了,特别是伴随着C++的继承机制,如野指针.无效指针使用.内存泄露.double free.堆碎片等等,这些问题就像地雷一样,一不小心就会踩那么几颗. 先来谈一下C

详解C++编程中向函数传递引用参数的用法

引用类型的函数参数 向函数传递引用而非大型对象的效率通常更高. 这使编译器能够在保持已用于访问对象的语法的同时传递对象的地址. 请考虑以下使用了 Date 结构的示例: // reference_type_function_arguments.cpp struct Date { short DayOfWeek; short Month; short Day; short Year; }; // Create a Julian date of the form DDDYYYY // from a

详谈C++引用&和指针在作为形参时的区别

int n; int &m = n; 在C++中,多了一个C语言没有的引用声明符&,如上,m就是n的引用,简单的说m就是n的别名,两者在内存中占同样的位置,不对m开辟新的内存空间,对m的任何操作,对n来说是一样的. 对于引用,有以下三条规则: (1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化). (2)不能有NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL). (3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象). 假如在一个函数中

C++ 中引用与指针的区别实例详解

C++ 中引用与指针的区别实例详解 引用是从C++才引入的,在C中不存在.为了搞清楚引用的概念,得先搞明白变量的定义及引用与变量的区别,变量的要素一共有两个:名称与空间. 引用不是变量,它仅仅是变量的别名,没有自己独立的空间,它只符合变量的"名称"这个要素,而"空间"这个要素并不满足.换句话说,引用需要与它所引用的变量共享同一个内存空间,对引用所做的改变实际上是对所引用的变量做出修改.并且引用在定义的时候就必须被初始化.     参数传递的类型及相关要点: 1 按值

详解C++11中的右值引用与移动语义

C++11的一个最主要的特性就是可以移动而非拷贝对象的能力.很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能. 右值与右值引用 为了支持移动操作,新标准引入了一种新的引用类型--右值引用,就是必须绑定到右值的引用.我们通过&&而不是&来获得右值引用.右值引用一个重要的特性就是只能绑定到将要销毁的对象. 左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值.一般而言,一个左值表达式表示的是一个对象的身份,而右

简单谈谈C++中指针与引用的区别

指针与引用是C++中两个很重要的概念,它们功能看过去很相似,就是都是间接引用某个对象,那么我们应该在什么时候使用指针,什么时候使用引用呢,下面请允许我慢慢道来: 1.永远不要使用一个指向空值的引用.一个引用必须始终指向某个对象,所以当你确定使用一个变量指向某个对象时,但是这个对象在某些时间可能指向控制,这时候你就必须把变量声明为指针类型,而不是引用!当你确定这个变量始终指向某个对象是,这时候你就可以把变量声明为引用类型. char *str=0; //设置指针为空值 char &s=*str;

C++常量详解一(常量指针与常量引用的初始化)

1.常量 1.1.常量的初始化: const对象一旦创建后其值就不能再改变,所以const对象必须初始化.这里我们要注意一点,像const int *p和const int &r都并不是const对象.因为const int *p只是表示不能通过p改变p所指的对象的值,p的值是可以变的,所以p可以不用初始化.至于r ,引用本身就不是对象,所以r也并不是const对象,r之所以一定初始化,是因为引用必须初始化.对于以上内容,你也可以理解为底层const 修饰的并不是const对象,还要注意像con

深入理解c++指针的指针和指针的引用

展示一下使用指针的指针和指针的引用修改传递给方法的指针,以便更好的使用它.(这里说的指针的指针不是一个二维数组) 为什么需要使用它们 当我们把一个指针做为参数传一个方法时,其实是把指针的复本传递给了方法,也可以说传递指针是指针的值传递. 如果我们在方法内部修改指针会出现问题,在方法里做修改只是修改的指针的copy而不是指针本身,原来的指针还保留着原来 的值.我们用下边的代码说明一下问题: int m_value = 1; void func(int *p) { p = &m_value; } i

golang方法中receiver为指针与不为指针的区别详析

前言 golang的指针receiver和非指针receiver的区别?最近在看网站有同学提问golang中方法的receiver为指针和不为指针有什么区别,在这里我以简单易懂的方法进行说明,帮助刚刚学习golang的同学,下面话不多说了,来一起看看详细的介绍吧. 方法是什么 其实只要明白这个原理,基本就能理解上面提到的问题. 方法其实就是一种特殊的函数,receiver就是隐式传入的第一实参. 举个例子 type test struct{ name string } func (t test)

C++指针数组、数组指针、数组名及二维数组技巧汇总

本文较为详细的分析了关于理解C++指针数组,数组指针,数组名,二维数组的一些技巧.是比较重要的概念,相信对于大家的C++程序设计有一定的帮助作用. 一.关于数组名 假设有数组: int a[3] = {1, 2, 3} 1.数组名代表数组第一个元素的地址,注意,不是数组地址(虽然值相等),是数组第一个元素地址,a 等同于 &a[0]; a+1是第二个元素的地址.比第一个元素地址a(或者&a[0])超出了一个整型指针的大小,在这里是4个字节(byte) cout << a <

简单分析C语言中指针数组与数组指针的区别

首先来分别看一下,指针数组的一个小例子: #include <stdio.h> #include <string.h> int lookup_keyword(const char*key, const char* table[], const int size) { int ret = -1; int i = 0; for(i=0; i<size; i++) { if (strcmp(key, table[i]) == 0) { ret = i; break; } } ret

简单总结C++中指针常量与常量指针的区别

我们先回顾下,什么是指针?什么是常量?指针是一种特殊的变量,它里面存储的内容是内存地址.常量是指其里面存储的内容不能发生改变的量.明白了这两个概念后,我们现在正式进入指针常量与常量指针. 1.指针常量与常量指针的概念 指针常量就是指针本身是常量,换句话说,就是指针里面所存储的内容(内存地址)是常量,不能改变.但是,内存地址所对应的内容是可以通过指针改变的. 常量指针就是指向常量的指针,换句话说,就是指针指向的是常量,它指向的内容不能发生改变,不能通过指针来修改它指向的内容.但是,指针自身不是常量

简要说明C语言中指针函数与函数指针的区别

指针函数一般是指返回指针的函数: #include <stdio.h> int* fun(int *a) { return a; } int main(int argc, char **argv) { int a = 3; printf("%d", *(fun(&a))); return 0; } 函数指针是表示指向函数开始地址的指针: 首先要了解函数的调用过程: #include <stdio.h> int fun(int i) { return i

C++普通函数指针与成员函数指针实例解析

C++的函数指针(function pointer)是通过指向函数的指针间接调用函数.相信很多人对指向一般函数的函数指针使用的比较多,而对指向类成员函数的函数指针则比较陌生.本文即对C++普通函数指针与成员函数指针进行实例解析. 一.普通函数指针 通常我们所说的函数指针指的是指向一般普通函数的指针.和其他指针一样,函数指针指向某种特定类型,所有被同一指针运用的函数必须具有相同的形参类型和返回类型. int (*pf)(int, int); // 声明函数指针 这里,pf指向的函数类型是int (

深入解析C++中的指针数组与指向指针的指针

指针数组定义:如果一个 数组,其元素均为指针型数据,该数组为指针数组,也就是说,指针数组中的每一个元素相当于一个指针变量,它的值都是地址. 形式:一维指针数组的定义形式为:int[类型名] *p[数组名] [4][数组长度];由于[ ]比*优先级高,因此p先与[4]结合,形成p[4]的数组的形式.然后与p前面的" * "结合," * "表示此数组是指针类型的,每个数组元素都相当于一个指针变量,都可以指向整形变量. 注意:不能写成int (*p)[4]的形式,这是指的

基于指针的数据类型与指针运算小结

1.指针的数据类型小结有关指针的数据类型 定义 含义 int i; 定义整形变量 int *p; 定义只想整型数据的指针变量p int a[n]; 定义整形数组a,它有n个元素 int *p[n]; 定义指针数组p,它由n个指向整形数据类型的指针元素组成 int (*p)[n]; 定义指向n个元素的一位数组的指针变量 int f(); f为带回整形函数值的函数 int *p(); p为带回一个指针的函数,该指针指向整形数据. int (*p)(); p为指向函数的指针,该函数返回一个整形数据 i

C++基类指针和派生类指针之间的转换方法讲解

函数重载.函数隐藏.函数覆盖 函数重载只会发生在同作用域中(或同一个类中),函数名称相同,但参数类型或参数个数不同. 函数重载不能通过函数的返回类型来区分,因为在函数返回之前我们并不知道函数的返回类型. 函数隐藏和函数覆盖只会发生在基类和派生类之间. 函数隐藏是指派生类中函数与基类中的函数同名,但是这个函数在基类中并没有被定义为虚函数,这种情况就是函数的隐藏. 所谓隐藏是指使用常规的调用方法,派生类对象访问这个函数时,会优先访问派生类中的这个函数,基类中的这个函数对派生类对象来说是隐藏起来的.

C++中指针函数与函数指针的使用

指针函数 指针函数是一个函数,只不过指针函数返回的类型是某一类型的指针. 格式: 类型名* 函数名(函数参数列表) 使用: /* * 指针函数,返回int* 指针变量 */ int* add(int a, int b) { int *p; int c = a + b; p = &c; return p; } int main() { int* p; p = add(1, 4); printf("%d\n", *p); getchar(); return 1; } 函数指针 函数