程序员面试(c++)——指针与引用

本文是对《程序员面试宝典》第七章——指针与引用的学习总结,不足之处,欢迎批评指正。

1、指针和引用的区别?

(1)指针可以指向空值,int* p=null;而引用则必须总是指向某个对象。

(2)指针在使用之前应该总要被测试是否合法,而引用则不需要。

(3)引用一旦指向某个对象,则不可以在指向其他对象,然而它指向对象的值是可以被修改的。

(4)由于以上不同决定了,指针和引用的应用是不同的。

2、传递动态内存

2.1

看下面的函数是否有错:

char* strA(){

char str[]="hello world";

return str;
}

上述代码是错误的,这个str存的是函数strA栈帧“hello world”的首地址,函数一旦调用完成,栈帧就恢复到调用strA之前的状态,strA栈帧不再属于被访问的范围。

那么上述函数改成下面的是否可行呢?

char* strA(){

char* str="hello world";

return str;
}

改成上述代码,就没问题了。为了弄清楚问题,我们首先理解清楚char* str和char str[]的区别:

char c[]="hello world"——是分配一个局部数组。

char* c="hello world"——是分配一个指针变量。

局部数组是局部变量,它对应的是内存中的栈,而指针变量是全局变量,它所对应的是内存中的全局区域。这就是上面代码可行的原因。另外,字符串常量保存在只读的数据段,而不是像全局变量那样保存在普通数据段(静态存储区)。

char *c="hello world";

*c='t'//这将是错误的,c占用了一个存储区域。

char c[]="hello world";

c[0]='t'//这是可以的,c不占用存储区域,局部区的数据是可以修改的。

2.2 指针和地址的关系

int a[3];a[0]=1;a[1]=1;a[2]=2;

int* p=a;

int* q=&a[2];

p的地址是a[0]的地址,q指向的是a[2]的地址,那么a[q-p]结果是什么呢?

实际上q-p的运算是:(q的地址值-p的地址值)/sizeof(int)。注意这里需要除以sizeof(int)

3、函数指针

3.1区别一下几种定义

const char* const * keywords;

这是一个二级指针,第一个const代表指针指向的变量是不可以修改的,第二个代表这是一个常量指针。(char** keywords)

const char const* keywords;

上述式子就相当于const char* keywords(实际上第二个const是修饰char的),因此上述表示一个指向const char的指针。

const char* const keywords;

第二个const是修饰指针的,说明这是一个指向const char的常量指针。

const char const keywords;

这是一个字符常量,等同于const char keywords。

注:可以认为*是右结合的来进行学习记忆。

3.2 容易和函数指针混淆的表达式

float (**def)[10];

这是一个二级指针,指向一个一维数组的指针,数组的元素都是float类型。float(*a)[10]则代表是一个一维指针,指针指向一个10元素的数组,数组元素都是float类型。

double* (*gh)[10];

gh是一个指针,指向一个10元素数组,数组的元素都是double*类型的。

double (*f[10])();

f是一个数组,有10个元素,元素都是函数的指针,指向的函数无参数,且返回类型为double。

int* ((*b)[10]);

等同于int* (*b)[10];b是一个指针,指向10元素的数组,数组元素是int*类型的。

long (*fun)(int);

这是一个函数指针fun,指向的函数参数类型为int,返回值为long。注意括号不可省略,括号省略之后的结果就变成

long *fun(int),那么这就变成一个函数声明,函数返回为long*类型的。

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

这个看着比较复杂,其实分解开来也很简单,F是一个函数的指针,指向的函数的类型是两个int参数,并且返回指针的函数,返回的函数指针指向有一个int参数且返回int的函数。分解开来就是这样的:

(*(*F)(int,int)):这是一个函数指针,即指向的函数参数为(int,int),返回值的是函数指针,具体指针指向的类型则由后半部分决定,第一个*是修饰(*F)(int,int)的,和剩余的结合起来表示的返回的是函数指针。

4、指针数组和数组指针

指针数组:主体是数组,数组元素是指针。

数组指针:主体是指针,指针指向数组。

例如:int (*a)[10]:右结合则可知这是一个数组指针,指针指向10个int元素的数组。

理解了这个,现在假设存在一个二维数组int v[2][10]={{...},{...}},并且让a=&v。由于a+1其实表明指针a向后偏移了1*sizeof(数组大小);因为指针指向的是10个int元素的数组。所以相当于向后移动了40个字节。*a代表二维数组v第一行元素,**a即代表第一行第一个元素,因此*a+1代表在二维数组第一行元素上向后移动sizeof(int)字节(a本来就是一个一维数组),其实就是移动了一个元素而已。

5、经过上述的学习,如果你能够按要求写出一下定义的话,那么说明你已经充分掌握了知识。

一个整型数——int a;

一个指向整型数的指针——int* a;

一个指向指针的指针,它指向的指针指向一个整型数——二维指针,int** a;

一个有10个整型数的数组——int a[10];

一个有10个指针的数组,该指针指向一个整型数——int *a[10];

一个指向10个整型数数组的指针——int (*a)[10];

一个函数指针,该函数有一个整型参数并返回一个整型数——int (*a)(int);

一个有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整型数——int (*a[10])(int);

其实上述式子都不难,只有我们做个有心人,一定可以将这些内容轻而易举的拿下。

现在你可能已经掌握了上述内容,那么再考考你,加入有以下代码:

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

int* ptr=(int*)(&a+1);

printf("%d %d",*(a+1),*(ptr-1));

输出结果是什么?

我们知道数组名即代表指针,因此a+1很容易可以计算得出是指向第二个元素,因此输出2.

那么第二个元素输出什么呢?

我们知道数组名a可以代表指针,因此&a就可以代表二维指针,因此&a+1则代表偏移了一整个数组,相当于偏移了5个元素,因此ptr指向第六个元素,ptr-1则指向了第五个元素。因此最终结果是5,你答对了吗?

6、迷途指针

什么是迷途指针?迷途指针又称为悬浮指针,当你释放(delete)内存时,并没有将指向这块内存的指针置为空指针,那么这个指针将成为迷途指针。迷途指针和空指针是完全不一样的两个概念。

既然说到释放内存(delete),我们知道c++里面已经有malloc/free,那为什么还需要new/delete?它们之间有什么不同?

malloc/free是c++/c里面的标准库函数,new/delete是c++的运算符。它们都可以用于动态申请和释放内存,但是对于非内部数据类型的对象而言,malloc/free是无法满足动态对象的要求的。对象在创建的时候自动运行构造函数,在消亡之前自动运行析构函数。由于它们是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和西沟函数的任务强加给malloc/free。因此c++语言需要一个完成内存分配和初始化工作的运算符new,以及清理与释放内存的运算符delete。为了更加容易理解,我们看看他们之间各自运行的工作都是什么?

malloc函数的参数是接受需要分配的内存字节数,如果内存能够满足请求量,那么将会返回指向被分配块的起始地址

free函数释放的是指针指向的内存(释放的不是指针,而是指针指向的内存,指针依然存在)

new会有两个事件发生:(1)内存被分配(2)为被分配的内存调用一个或多个构造函数构建对象。int* p=new int;

delete也会有两个事件发生:(1)为被释放的内存调用一个或多个析构函数(2)释放内存。

如果想要更深入地学习new和delete,可以参考这个书《深入探索c++对象模型》

7、说到指针,有一个不得不提的指针,那就是this指针

this指针常常容易混淆的概念有如下:

this指针时时刻刻指向实例本身。

(1)this指针本质上是一个函数参数,只是编译器隐藏起形式的、语法层面上的参数。this只能在成员函数中使用,全局函数和静态函数都不可以this指针,因为this指针是指向这个实例的。实际上,成员函数默认的第一个参数为T* const this:如下

class A{

public:

int fun(int p){}
};

实际上是等价于int fun(A* const this, int p);

(2)this在成员函数的开始执行前构造,在成员函数执行结束后清除。这个周期内同任何函数的参数没有什么区别。当调用一个类的成员函数时,编译器将类的指针作为函数的this参数传入进去。例如:

A a;

a.fun(10);编译器将解释为fun(&a, 10);this即指向这个实例的指针。

(3)this指针并不占用对象的空间。

所有成员函数的参数,不管是不是隐含的,都不会占用对象的空间,只会占用参数传递的栈空间,或者直接占用一个寄存器。

(4)this指针是什么时候开始创建的呢?

this指针在成员函数的开始执行前构造,在成员函数执行结束后清楚。

(5)this指针存放在何处?

this指针会因为不同的编译器而放置在不同的位置,可能是堆、栈,也可能是寄存器。

语法上,this是个指向对象的“常指针”,因此无法改变,它是一个指向相应对象的指针,所有对象的共同成员函数利用这个指针进行区别不同变量,this是“不同对象共享相同成员函数”保证。

(6)this指针是如何传递给类中的函数的呢?

大多数编译器通过寄存器ecx传递this指针。

(7)我们只有获得一个对象后,才能通过对象使用this指针,如果我们知道一个对象的this指针,那么我们可以直接使用吗?

this指针只有在成员函数中才有定义。因此,当你获得一个对象时,也不能通过对象使用this指针。所以我们无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置),当然如果在成员函数中,我们是可以得到this指针的位置&this,因此我们当然能够使用它。








发布了65 篇原创文章 · 获赞 1 · 访问量 5万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章