C/C++中指针和引用之相关问题深入研究_C 语言

一、基本知识
指针和引用的声明方式:
声明指针: char* pc;
声明引用: char c = 'A'
   char& rc = c;

它们的区别:
①从现象上看,指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变。这句话可以理解为:指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。

②从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域,因为引用声明时必须初始化,从而指向一个已经存在的对象。引用不能指向空值。

③从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。

④不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。

⑤理论上,对于指针的级数没有限制,但是引用只能是一级。

如下:

复制代码 代码如下:

  int** p1; // 合法。指向指针的指针
  int*& p2; // 合法。指向指针的引用
  int&* p3; // 非法。指向引用的指针是非法的
  int&& p4; // 非法。指向引用的引用是非法的

注意上述读法是从左到右。

程序1:

复制代码 代码如下:

#include "stdio.h"
int main(void)
{
  // 声明一个char型指针pc,且让它指向空值
  char* pc = 0;
  char a = 'a';
  // 声明一个引用rc,且让它引用变量a
  char& rc = a;
  printf("%d, %c\n", pc, rc);

  char *pc2;
  // 声明一个指针,但可以不初始化
  pc2 = pc;

  // char& rc2;
  // 上面语句编译时,会产生如下错误:
  // error C2530: 'rc2' : references must be initialized
  // 即,应用必须初始化
  // rc = *pc;
  // 上面语句编译不会有问题,但运行时,会报如下错误:
  // "0x00401057"指令引用的"0x00000000"内存。该内存不能为"read"
  // 说明引用在任何情况下,都不能指向空值

  return 0;
}

程序2:

复制代码 代码如下:

#include <iostream>
#include <string>
using namespace std;
int main(void)
{
  string s1("Hello");
  string s2("World");
  // printf("%s\n", s1); 不能用printf输出s1,而应该用cout

  cout << "s1的地址 = "<< &s1 << endl;// &s1 = 0012FF64
  cout << "s2的地址 = "<< &s2 << endl;// &s2 = 0012FF54

  string& rs = s1;   // 1. 定义一个引用rs,rs引用s1
  cout << "引用rs的地址 = " << &rs << endl;  // &rs = 0012FF64

  string* ps = &s1; //定义一个指针ps, ps指向s1
  cout << "指针ps的地址 = " << ps << endl;// ps = 0012FF64

  cout << rs << ", " << *ps << endl;  // Hello, Hello
  // 如果没有#include <string>,上面的语句在编译的时候,会出现如下错误:
  // error C2679: binary '<<' : no operator defined which takes a right-
  // hand operand of type 'class std::basic_string<char,struct
  // std::char_traits<char>,class std::allocator<char> >'
  // (or there is no acceptable  conversion)

  rs = s2;  // 2. rs仍旧引用s1, 但是s1现在的值是"World"
  ps = &s2;   // ps现在指向s2

  cout << "引用rs的地址 = " << &rs << endl;  // &rs = 0012FF64 未改变
  cout << "引用rs的值 = " << rs << endl;   // rs = "World" 已改变

  cout << "指针ps的地址 = " << ps << endl;// ps = 0012FF54  已改变
  cout << "指针ps所指地址的内容 = " << *ps << endl;  // *ps = World已改变

  cout << "s1的地址 = "<< &s1 << endl;// 3. &s1 = 0012FF64 未改变
  cout << "s1的值 = " << s1 << endl; // 4. s1 = World  已改变

  return 0;
}

可以认为:
引用就是变量的别名,在引用初始化的时候就已经确定,以后不能再改变。见程序2的粗体字语句。第1句,声明了rs引用s1,s1的值为”Hello”,从这以后,rs实际上就相当于变量s1了,或者从更本质的意义上来说,rs的地址就是初始化时s1的地址了,以后都不会再改变。这应该比较好理解,比如我们在程序中定义了一个变量a,不管我们如何给a赋值,但它的地址是不会改变的;

第2句,rs仍旧指向初始化时s1的地址,但此处的赋值就相当于重新给s1赋值,因此我们从第3句和第4句可以看到,s1的地址并没有发生变化,但是其值已经发生了变化。

二、作为参数传递
利用引用的这个特性,可以用它作为函数的传出参数。如程序3:

复制代码 代码如下:

#include <iostream>
#include <string>
using namespace std;
int newEvaluation(string& aStr)
{
  string bStr("Hello,");
  aStr = bStr + aStr;

  return 0;
}

int main(void)
{
  string aStr("Patrick!");
  newEvaluation(aStr);
  std::cout << aStr << endl; // 输出结果:"Hello, Patrick!"

  return 0;
}

而一般变量,则不能从函数内部传值出来,比如程序4:

复制代码 代码如下:

#include <iostream>
#include <string>
using namespace std;

int newEvaluation(string aStr)
{
  string bStr("Hello,");
  aStr = bStr + aStr;

  return 0;
}

int main(void)
{
  string aStr("Patrick!");
  newEvaluation(aStr);
  std::cout << aStr << endl; // 输出结果:"Patrick!",aStr的值没有变化

  return 0;
}

当然程序3引用传递的方式也可以写成指针传递的方式,如程序5:

复制代码 代码如下:

#include <iostream>
#include <string>
using namespace std;

int newEvaluation(string* const aStr)
{
  string bStr("Hello,");
  *aStr = bStr + *aStr;

  return 0;
}

int main(void)
{
  string aStr("Patrick!");
  newEvaluation(&aStr);
  std::cout << aStr << endl; // 输出结果:"Hello, Patrick!"

  return 0;
}

注意程序中的陷井,如程序6:

复制代码 代码如下:

#include <iostream.h>
int *pPointer;
void SomeFunction()
{
  int nNumber;
  nNumber = 25;
  //让指针指向nNumber
  pPointer = &nNumber;
}

void main()
{
  SomeFunction();//为pPointer赋值
  //为什么这里失败了?为什么没有得到25
  cout << "Value of *pPointer: " << *pPointer << endl;
}

这段程序先调用了SomeFunction函数,创建了个叫nNumber的变量,接着让指针pPointer指向了它。可是问题出在哪儿呢?当函数结束后,nNumber被删掉了,因为这一个局部变量。局部变量在定义它的函数执行完后都会被系统自动删掉。也就是说当SomeFunction 函数返回主函数main()时,这个变量已经被删掉,但pPointer还指着变量曾经用过的但现在已不属于这个程序的区域。

尽管在SomeFunction中使用所谓的动态分配内存。程序7中也存在陷井:

复制代码 代码如下:

#include <iostream.h>
int *pPointer;

void SomeFunction()
{
int intNumber = 25;
// 让指针指向一个新的整型
pPointer = new int;
pPointer = &intNumber;
}

void main()
{
SomeFunction();   // 为pPointer赋值
cout<< "Value of *pPointer: " << *pPointer << endl;
delete pPointer;
}

原因也如上面所言,intNumber的作用范围仅限于SomeFunction中,离开了SomeFunction,那么intNumber就不存在了,那么&intNumber即intNumber的地址就变得没有意义了,因此,该地址所指向的值是不确定的。如果改为下面的程序就不会有问题了。

程序8:

复制代码 代码如下:

#include <iostream.h>
int *pPointer;

void SomeFunction()
{
int intNumber = 25;
// 让指针指向一个新的整型
pPointer = new int(intNumber);
}

void main()
{
SomeFunction();   // 为pPointer赋值
cout<< "Value of *pPointer: " << *pPointer << endl;
delete pPointer;
}

三、指针的指针
前面说到,指针是没有级数限制的。
程序9:

复制代码 代码如下:

#include<stdio.h>
#include<stdlib.h>

void main(void)
{
int i, j;
int a[10], b[3][4], *p1, *p2, **p3;  
for(i = 0; i < 10; i++)
   scanf("%d", &a[i]);  

for(i = 0; i < 3; i++)
   for(j = 0; j < 4; j++)
   scanf("%d", &b[i][j]);

p1 = a;
p3 = &p1;
for(i = 0; i < 10; i++)
   printf("%4d", *(*p3+i));
printf("\n");

for(p1 = a; p1 - a < 10; p1++)
{
   p3 = &p1;
   printf("%4d", **p3);
}
printf("\n");

for(i = 0; i < 3; i++)
{
   p2 = b[i];
   p3 = &p2;
   for(j = 0; j < 4; j++)
   printf("%4d",*(*p3+j));
   printf("\n");
}

for(i = 0; i < 3; i++)
{
   p2 = b[i];
   for(p2 = b[i]; p2-b[i] < 4; p2++)
   {
   p3 = &p2;
   printf("%4d", **p3);
   }
   printf("\n");
}
}

输出的结果:
1   2   3   4   5   6   7   8   9   10
1   2   3   4   5   6   7   8   9   10
11  12  13  14
15  16  17  18
19  20  21  22
11  12  13  14
15  16  17  18
19  20  21  22

四、函数指针和函数引用
函数指针是C++最大的优点之一。和使用普通指针相比,高级程序员只要有可能都更愿意使用引用,因为引用更容易处理一些。然而,当处理函数时,函数引用对比函数指针就未必有这个优势了。现有的代码很少使用函数引用。下面将向介绍如何函数指针、如何使用函数引用以及分别在什么情况下使用它们。

① 函数指针的例子

复制代码 代码如下:

#include <iostream>
void print(int i)
{
std::cout << i << std::endl;
}

void multiply(int& nDest, int nBy)
{
nDest *= nBy;
}

void print_something()
{
std::cout << "something" << std::endl;
}

int sayHello()
{
std::cout << "Hello, World!" << std::endl;
return 10;
}

int main()
{
void (*pFunction_1)(int);
pFunction_1 = &print;
pFunction_1(1);
// 输出结果为1

void (*pFunction_2)(int&, int) = &multiply;
int i = 1;
pFunction_2(i, 10);
std::cout << "i = " << i << std::endl;
// 输出结果为10

void (*pFunction_3)();
pFunction_3 = &print_something;
pFunction_3();
// 输出结果为something

int (*pFunction_4)();
pFunction_4 = &sayHello;
int a = pFunction_4();
// 输出结果为Hello, World!
std::cout << a << std::endl;
// 输出结果为10

return 0;
}

② 函数引用的例子

复制代码 代码如下:

#include <iostream>
void print(int i)
{
std::cout << i << std::endl;
}

void print2(int i)
{
std::cout << i << std::endl;
}

void multiply(int& nDest, int nBy)
{
nDest *= nBy;
}

void print_something()
{
std::cout << "something" << std::endl;
}

int sayHello()
{
std::cout << "Hello, World!" << std::endl;
return 10;
}

 
int main()

// void (&rFunction_1)(int);
// 错误:未初始化引用!引用必须初始化

void (&rFunction_2)(int) = print;
rFunction_2(1);
// 输出1

rFunction_2 = print2;
rFunction_2(2);
// 输出2

void (&rFunction_3)(int&, int) = multiply;
int i = 1;
rFunction_3(i, 10);
std::cout << i << std::endl;
// 输出10

void (&rFunction_4)() = print_something;
rFunction_4();
// 输出something

int (&rFunction_5)();
rFunction_5 = sayHello;
int a = rFunction_5();   // 输出Hello, World!
std::cout << a << std::endl;
// 输出10

return 0;
}

③ 函数指针和函数引用作为函数参数

复制代码 代码如下:

#include <iostream>

void print(int i)
{
std::cout << i << std::endl;
}

void print2(int i)
{
std::cout << i * 2 << std::endl;
}

void printSomething()
{
std::cout << "Something" << std::endl;
}

void sayHello()
{
std::cout << "Hello, World!" << std::endl;
}

void call_p_func(void (*func)(int))
{
func(1);
func(2);
func(3);
}

void call_r_func(void (&func)(int))
{
func(1);
func(2);
func(3);
}

void call_p_function(void (*func)())
{
func();
}

int main()
{
std::cout << "函数指针作为参数" << std::endl;
call_p_func(&print);
call_p_func(&print2);
call_p_function(&printSomething);
call_p_function(&sayHello);
call_p_function(sayHello);
// 上面两句对于某些编译器来说是一样的,但是推荐使用前者的写法,
// 这样可以是程序的可读性更好一些

std::cout << "函数引用作为参数" << std::endl;
call_r_func(print);
call_r_func(print2);

return 0;
}

总结:
函数指针的声明使用方式:
<想要指向的函数之返回类型>(*函数指针的名称)<想要指向的函数之参数类型…>
如要想声明一个函数指针指向以下函数:

复制代码 代码如下:

void print(int i)
{
std::cout << i << std::endl;
}

那么就可以如下操作:
void (*pFunction)(int);
然后如下用函数的地址给pFunction赋值:
pFunction = &print;
在然后,pFunction就可以和函数print一样使用了,比如,
pFunction(1);
等等。

函数引用的声明和使用方式:
<欲引用的函数之返回类型>(&函数引用的名称)<欲引用的函数之参数类型…>=<欲引用的函数的名称>,至所以如此,是引用在声明的时候必须初始化,引用不能指向空值。
如要想声明一个函数引用指向以下函数:

复制代码 代码如下:

void print(int i)
{
std::cout << i << std::endl;
}

那么就可以如下操作:
void (&rFunction)(int)=print;
在然后,rFunction就可以和函数print一样使用了,比如,
rFunction(1);
等等。

五、const修饰指针和引用
大致而言,const修饰指针和引用分三种情况,即const修饰指针、const修饰引用和const修饰指针的引用。下面分别讨论之。

① const修饰指针
const修饰指针又分为三种情况,即const修饰指针本身、const修饰指针所指的变量(或对象)以及const修饰指针本身和指针所指的变量(或对象)。

a. const修饰指针本身
在这种情况下,指针本身是常量,不能改变,任何修改指针本身的行为都是非法的,例如:
double pi = 3.1416;
double* const PI = π

double alpha = 3.14;
PI = α   // 错误。因为指针PI是常量,不能再被改变。
*PI = alpha;   // OK。虽然指针PI不能被改变,但指针所指的变量或者对象可变。

b. const修饰指针指向的变量(或对象)
在这种情况下,指针本身可以改变,但const所修饰的指针所指向的对象不能被改变,例如:
double pi = 3.1416;
const double* PI = π

double alpha = 3.14;
*PI = alpha;// 错误。因为PI所指向的内容是常量,因此*PI不能被改变。
PI = α// OK。虽然指针所指的内容不能被改变,但指针PI本身可改变。从而通过这种方式改变*PI。

c. const修饰指针本身和指针所指向的变量(或对象)
在这种情况下,指针本身和指针指向的变量(或对象)均不能被改变,例如:
double pi = 3.146;
const double* const PI = π
//double const* const PI = π
cout << "PI = " << PI << endl;
cout << "*PI = " << *PI << endl;

double alpha = 3.14;
//*PI = alpha; // 错误。因为PI所指向的内容是常量,因此*PI不能被改变。
//PI = α // 错误。因为指针PI是常量,不能再被改变。

② const修饰引用
const修饰引用没有指针修饰指针那么复杂,只有一种形式。引用本身不能被改变,但所指向的对象是可以被改变的,见上面“一、基本知识”。
double pi = 3.1416;
//const double& PI = pi;
double const& PI = pi;  //和上面一句是等价的
//double& const PI = pi;//有问题。很多编译器会产生warning
cout << PI << endl;

③ const修饰指针引用
我们用例子来说明。
double pi = 3.14;
const double* pPI = π
//const double*& rPI = π //错误。不能将double* 转换成const double *&
const double*& rPI = pPI;   //OK。声明指针引用的正确方法

说明:const double*& rPI = π 为什么会出现错误呢?我们知道,引用是被引用对象的别名,正因为如此,由于rPI是pPI的别名,因此rPI和pPI的类型必须完全一致。从上面的代码段我们可以看到,rPI的类型是const double*,而π的类型是double*,因此这句程序是错误的。

下面这段代码和 ① 中的b中的情形对应(即内容不可变,指针可变):
double pi = 3.1416;
double api = 3.14;
const double* pPI = π
const double* pAPI = &api;
const double*& rPI = pPI;
const double*& rAPI = pPI;

*rAPI = api; // 错误。指针所指向的值不能被直接改变
rAPI = pAPI;   // OK。指针本身可以被改变

指针引用的用法还有其它的情形,由于罕用,故此不谈及。

时间: 2024-10-13 20:45:18

C/C++中指针和引用之相关问题深入研究_C 语言的相关文章

c++ 指针与引用的区别介绍及使用说明_C 语言

指针与引用看上去完全不同(指针用操作符"*"和"->",引用使用操作符"."),但是它们似乎有相同的功能.指针与引用都是让你间接引用其他对象.你如何决定在什么时候使用指针,在什么时候使用引用呢? 首先,要认识到在任何情况下都不能使用指向空值的引用.一个引用必须总是指向某些对象.因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量.相反,如果变量肯定指向一

C语言的fork函数在Linux中的进程操作及相关面试题讲解_C 语言

fork的意义 下图为,C 程序的存储空间布局(典型) 1.一个现有进程可以调用 fork 函数创建一个新进程. 2.fork 函数被调用一次,但返回两次, 两次返回的唯一区别是子进程的返回值是 0, 而父进程的返回值是新子进程的 PID. 3.子进程和父进程继续执行 fork 调用之后的指令. 在上图的存储空间布局中,父子进程只共享正文段,其余的都各自有独立的副本 (通常使用 copy-on-write 的策略,速度比较快). fork 的两种用法 1.父子进程同时执行不同的代码段典型应用:W

C++中指针和引用的区别分析_C 语言

从概念上讲.指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变. 而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量). 在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的: 指针传递参数本质上是值传递的方式,它所传递的是一个地址值.值传递过程中,被调

C++中指针的引用详解

指针(*)和引用(&),解引用(*)和取地址(&), 主要观察是在左面, 还是在右面, 如果在左面是前者, 在右面是后者; 如果比较多, 则要认真观察, 譬如int*&, 就是指针的引用, 可以避免指针内部元素的复制; 还有其他的一些形式, 见下面的例子; 代码: /* * test.cpp * * Created on: 2013.11.12 * Author: Caroline */ /*eclipse cdt; gcc 4.7.1*/ #include <iostrea

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

引用的意义与本质1)引用作为其它变量的别名而存在,因此在一些场合可以代替指针 2)引用相对于指针来说具有更好的可读性和实用性 引用本质思考: 思考.C++编译器背后做了什么工作? #include <iostream> using namespace std; int main() { int a = 10; // 单独定义的引用时,必须初始化:说明很像一个常量 int &b = a; // b是a的别名 b = 11; cout << "b--->&quo

C++中的auto_ptr智能指针的作用及使用方法详解_C 语言

智能指针(auto_ptr) 这个名字听起来很酷是不是?其实auto_ptr 只是C++标准库提供的一个类模板,它与传统的new/delete控制内存相比有一定优势,但也有其局限.本文总结的8个问题足以涵盖auto_ptr的大部分内容.  auto_ptr是什么? auto_ptr 是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个拥有者.当auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥

C++中虚函数与纯虚函数的用法_C 语言

本文较为深入的分析了C++中虚函数与纯虚函数的用法,对于学习和掌握面向对象程序设计来说是至关重要的.具体内容如下: 首先,面向对象程序设计(object-oriented programming)的核心思想是数据抽象.继承.动态绑定.通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象. 虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用

解析C++中四种强制类型转换的区别详解_C 语言

C++的四种强制类型转换,所以C++不是类型安全的.分别为:static_cast , dynamic_cast , const_cast , reinterpret_cast为什么使用C风格的强制转换可以把想要的任何东西转换成合乎心意的类型.那为什么还需要一个新的C++类型的强制转换呢?新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换.C++中风格是static_cast<type>(content).C++风格的强制转换其他的好处是,它们能更清晰的表明它们要干

C/C++中static,const,inline三种关键字详细总结_C 语言

一.关于staticstatic 是C++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 修饰符的产生原因.作用谈起,全面分析static 修饰符的实质. static 的两大作用: 一.控制存储方式 static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间. 引出原因:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保