程序员面试(c++)——指针与引用
2016-06-29 20:55
204 查看
本文是对《程序员面试宝典》第七章——指针与引用的学习总结,不足之处,欢迎批评指正。
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,因此我们当然能够使用它。
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,因此我们当然能够使用它。
相关文章推荐
- 浅谈C++中指针和引用的区别
- Java进阶:细说引用类型
- C++中常问道一些基本概念整理
- C++基础系列之要点总结(1)
- C++入门知识
- c++常考题
- 程序员面试(c++)——面向对象
- 经典问题解析(1)---const和引用、指针与引用、函数重载、C方式编译
- C++ 类的继承一(访问控制)
- C++ 11 创建和使用 unique_ptr
- C++容器的insert()函数有以下三种用法: 最终*it=val;
- 栈练习之C语言中实现中缀转后缀表达式
- 图片处理(四)之边沿检测
- 传统高斯模糊与优化算法(附完整C++代码)
- c++ new高级用法
- C++中substr函数的用法
- C语言内存浅谈
- c++动态联编和静态联编
- c++虚函数
- c++纯虚函数