《王道》指针与引用

《王道》指针与引用

1 指针

1.1 指针的声明

    一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一对象;或者是0值/NULL。若指针保存0值/NULL,表明它不指向任何对象。未初始化的指针是无效的,直到给该指针赋值后,才可使用它。

    C++中使用*把一个标识符声明为一个指针,指针定义的一般形式为:

    数据类型 *指针变量名;

    例如:

char *pc;
int *pi;
doule *pd = NULL;

    第三行定义了一个指针并初始化为NULL,表示该指针不指向任何变量。

    注意:第一行和第二行定义的指针未初始化,这是相当危险的。因为未初始化的指针随机指向内存中一个地址,对该地址空间的写操作其后果不能确定,所以应避免使用未初始化的指针,在定义指针时最好将它初始化为NULL。

    例1 下列定义和赋值哪些是合法的?

int ival = 1024;
int *pi = 0;    //合法,定义了一个指针并初始化为NULL,表示该指针不指向任何变量
int *pi0 = 5;   //非法,除了0之外,指针不能被int型的数据初始化
int *pi1;       //合法,但是指针未初始化,这是相当危险的
*pi1 = 5;       //非法,因为指针pi1未初始化,所以不能使用*取值操作
int *pi2 = &ival;   //合法,pi2初始化为ival的地址
pi = pi2;           //合法,指针pi和pi2指向相同的地址
pi2 = 0;            //合法,pi2现在不指向任何变量

    例2  *p++,(*p)++,*++p,++*p有什么不同

     注意,下面的每条cout输出,要单独输出才能得到后面的结果。

int a[5]={1,2,3,4,5};

    int *p = a;

    *p++ 先取指针p指向的值(数组第一个元素1),再将指针p自增1;

 cout << *p++; //  结果为 1
 cout <<(*p++);  // 1

    (*p)++ 先取指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2)

cout << (*p)++; //  1
cout <<((*p)++)  //2 疑问:VS2016运行结果为1而不是2

    *++p  先将指针p自增1(此时指向数组第二个元素),* 操作再取出该值      

 cout << *++p; //  2
 cout <<(*++p)  //2

    ++*p  先取指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2)

 cout <<++*p; //   2    
 cout <<(++*p)  //2

1.2 typedef与define

    参考博客

    C语言允许用typedef说明一种新类型名,来代替已有类型名,形式为:

          typedef  类型名 标识符

    其中,类型名是在此语句前已有的类型标识符,标识符是用作新类型名的用户自定义标识符。

    例如:

typedef float REAL;
typedef char* PCHAR;

REAL a, b;   //等价于float a,b;
PCHAR p;     //等价于char *p;

    也就是说,typedef并未产生新的数据类型,它的作用仅仅是给已存在的类型名起一个别名,且原有类型名依然有效。

    例3 typedef char* String_t; 和 #define String_d char*这两句在使用上有什么区别?

    前者声明一个类型的别名,在编译时处理,有类型检查;后者是一个简单的替换,在预编译时处理,无类型检查。从使用上来说,宏定义只是简单的字符串代换(原地扩展),而typedef则不是原地扩展,它的新名字具有一定的封装性,以致于新命名的标识符具有更易定义变量的功能。例如:"String_t a,b;"等价于"char* a; char*b;",而"String_d a,b;"等价于"char* a,b;",即b是char类型。

    PS: #define 不是语句,不要在行末加分号,否则会连分号一块置换。

    例4 在typedef中定义指针往往会带来意外的结果,假设给出以下语句:

typedef string *pstring;
const pstring cstr;

    请问cstr变量是什么类型?简单的回答是const pstring类型的指针。进一步问:const pstring 指针所表示的真实类型是什么?很多人认为真实类型是:

const string *cstr;

    也就是说,cstr是一种指针,指向string类型的const对象,但这是错误的。错误的原因在于将typedef当作宏替换似的文本扩展了。声明const pstring时,const修饰的是pstring的类型,这是一个指针。因此,该声明语句应该是把cstr定义为指向string类型对象的const指针,正确应为:  

string *const cstr;

1.3 void* 指针

    参考博客

    C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址:

double obj = 3.14;
double *pd = &obj;
void *pv = &obj;
pv = pd;    //pd=pv;错误

    void* 表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型。void*指针只支持几种类型的操作:

    1)与另一个指针比较;

    2)向函数传递void*指针或从函数返回void*指针;

    3)给另一个void*指针赋值;

    PS: 不允许使用void*指针操纵它所指向的对象。

    需要注意,当函数返回void*类型时表示返回一个特殊的指针类型,而不是像函数返回类型为void那样表示无返回值。

1.4 指向指针的指针

  指针本身也是可以用指针指向的内存对象。指针占用内存空间存放其值(值为地址),因此指针的存储地址可存放在指针中。

    参考博客

   例5 在32位系统下,写出下述代码的输出结果。

#include <iostream>
using namespace std;
int main()
{
	double* (*a)[3][6];
	cout << sizeof(a) << endl;
	cout << sizeof(*a) << endl;
	cout << sizeof(**a) << endl;
	cout << sizeof(***a) << endl;
	cout << sizeof(****a) << endl;
	system("pause");
	return 0;
}


1.5 函数指针

    函数指针指向的是函数而非对象,和其他指针一样,函数指针指向某种特定类型,函数的类型由它的返回类型和形参类型共同决定,与函数名无关。

    例如:

bool LengthCompare(const string &, const string &)

    该函数的类型是bool(const string& ,const string&)。想要声明一个指向该函数的指针,只需要用指针替换函数名即可:

bool(*pf)(const string&, const string&);//未初始化

    注意:*pf两侧的圆括号是必需的。

    赋值我们可以通过两种方法赋值:

pf = LengthCompare;
pf = &LengthCompare;

用typedef简化函数指针的定义

    函数指针类型相当冗长,使用typedef为指针类型定义同义词,可将函数指针的使用大大简化:

typedef bool(*cmpFcn)(const string&, const string&);

    该定义表示cmpFcn是一种指向函数的指针类型的名字。该指针类型为“指向返回bool类型并带有两个const string 引用形参的函数的指针”。在要使用这种函数指针类型时,只需直接使用cmpFcn即可,不必每次都把整个类型声明全部写出来。

函数指针的使用

    我们还可以直接使用指向函数的指针调用函数,无须提前解引用:

bool b1 = pf("hello", "goodbye");
bool b2 = (*pf)("hello", "goodbye");
bool b3 = LengthCompare("hello", "goodbye");
//三个等价调用

    指向不同函数类型的指针之间不存在转换。

函数指针形参

    形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上确实当成指针使用:

//第三个形参是函数类型,它会自动地转成指向函数类型的指针
void useBigger(const string &s1,const string &s2,bool pf(const string &,const string&));
//等价
void useBigger(const string &s1,const string &s2,bool (*pf)(const string&,const string&));

useBigger(s1,s2,lengthCompare);

返回指向函数的指针

    函数可以返回指向函数的指针,但是,正确写出这种返回类型相当不容易:

int(*ff(int)(int*, int));

    阅读函数指针声明的最佳方法是从声明的名字开始由里向外理解。

    要理解该声明的含义,首先观察:

ff(int)

    将ff声明为一个函数,它带有一个int型的形参,该函数返回:

int (*) (int*, int));

    它是一个指向函数的指针,所指向的函数返回int类型并带有两个分别是int*和int型的形参。使用typedef可使定义更简明易懂:

typedef int (*PF) (int*, int);
PF ff(int);    //函数ff返回一个函数指针

指向重载函数的指针

    C++语言允许使用函数指针指向重载的函数:

extern void ff(vector<double>);
extern void ff(unsigned int);

    若有:

void(*pf1) (unsigned int) = &ff;

    则pf1指向参数为unsigned int 版本的函数ff。

    指针的类型必须与重载函数的一个版本精确匹配。如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误。

写一个函数指针、一个函数指针数组、一个指向指针数组的指针

    1)函数指针  

#include<stdio.h>  
#include<Windows.h>  
void fun()
{
	printf("我调用成功了\n");
}
int main()
{
	void(*pfun)() = &fun;
	(*pfun)();
	system("pause");
	return 0;
}

    2)函数指针数组

    函数指针数组,是每一个元素为函数指针的数组。

#include<stdio.h>  
#include<Windows.h>  
void fun1()
{
	printf("我调用成功了\n");
}
void fun2()
{
	printf("我第二次调用成功了\n");
}
void fun3()
{
	printf("我第三次调用成功了\n");
}
int main()
{

	void(*pfun)() = &fun1;
	void(*pfun2)() = &fun2;
	void(*pfun3)() = &fun3;
	//接下来就是定义一个数组把他们三个装进去。  
	void(*pfunarr[3])();
	//一个具有3个以函数指针为内容的元素的函数指针数组。  
	pfunarr[0] = pfun;
	pfunarr[1] = pfun2;
	pfunarr[2] = pfun3;

	pfunarr[0]();
	pfunarr[1]();
	pfunarr[2]();
	system("pause");
	return 0;
}






    3)指向指针数组的指针

#include<stdio.h>  
#include<Windows.h>  
void fun1()
{
	printf("我调用成功了\n");
}
void fun2()
{
	printf("我第二次调用成功了\n");
}
void fun3()
{
	printf("我第三次调用成功了\n");
}
int main()
{
  
	void(*pfunarr[3])();
	void(*(*pfunarr2)[3])();
	pfunarr[0] = &fun1;
	pfunarr[1] = &fun2;
	pfunarr[2] = &fun3;
	pfunarr2 = &pfunarr;
	(*pfunarr2)[0]();
	pfunarr[0]();
	system("pause");
	return 0;
}

2 引用

    引用就是对象的另一个名字。所谓的引用,其实就是一个特殊的变量,这个变量的内容是绑定在这个引用上面的对象的地址,而使用这个变量时,系统就会根据这个地址去找到它绑定的变量,然后再对变量进行操作。所以本质上说,引用其实还是指针,只不过这个指针是不能修改的,任何对他的操作都会发生在这个指针所指向的地方,而不是在这个指针身上。所以说,C++中规定一旦定义了引用,就必须把它跟一个变量绑定起来,并且不能修改这个绑定。

    不能定义引用类型的引用,但可以定义任何其他类型的引用。

    虽然使用引用(reference)和指针都可以间接访问另一个值,但它们之间有几个重要区别:

    1)引用不能为空,当引用被创建时,必须被初始化。而指针可以为空值,可以在任何时候被初始化;

    2)一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用。指针则可以在任何时候指向另一个对象;

    3)不可能有null引用。必须确保引用是和一块合法的存储单元关联;

    4)sizeof(引用)得到的是所指向的变量(对象)的大小,而sizeof(指针)得到的是指针本身的大小;

    5)给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象相关联;

    6)引用使用时不需解引用,而指针需解引用,引用和指针的自增(++)操作运算意义不一样;

    7)如果返回动态分配的对象或内存,必须使用指针,引用可能引起内存泄露;

    8)当使用&运算符取一个引用的地址时,其值为所引用的变量的地址;而对指针使用&运算,取的是指针变量的地址。

    例6  下列代码的输出结果是什么?


const引用

    const引用是指向const对象的引用,当引用的对象是const对象时,引用也必须是const,如下:

    

    

引用做类的数据成员

    引用是可以作为类的数据成员的。

    引用类型数据成员的初始化有以下特点:

    1)不能直接在构造函数里初始化,必须用到初始化列表;

    2)凡是有引用类型的数据成员的类,必须定义构造函数;


3 练习

    1.给出下面常用的C变量的定义方式。

    (1)一个含有10个整数型指针的数组

    (2)一个指向含10个int型整数数组的指针

    (3)一个函数指针数组,指向的函数形参为整数型,返回为整数型

    解答:

    (1)int* p[10];

    (2)int (*p)[10];

    (3)int (*p[10])(int);

    2.什么是野指针?





猜你喜欢

转载自blog.csdn.net/qq_27022241/article/details/80234749