目录
在我的博客C++高质量编程-CSDN博客内存管理章节里面讲了一些内存的基础知识,内存管理涉及最多的就是指针,也详细讲解了指针的原理以及平时编程使用指针犯的错误,不太熟悉的可以去看看,现在就对面试问的比较多的关于指针的问题归纳总结一下,希望对看到这篇博客的同学们有所帮助。
1.什么是指针?请简述指针的基本概念和用途。
2.指针变量和普通变量有什么区别?如何声明和初始化指针变量?
3.指针的算术运算有哪些?请举例说明
4.什么是野指针?如何避免野指针的产生?
5.指针和引用区别?
6.什么是函数指针?请简述函数指针的概念和用途
7.如何声明和初始化函数指针?如何通过函数指针调用函数?
8.什么是多级指针?请举例说明多级指针的使用场景。
9.指针和数组有何关系?如何通过指针访问数组元素?
10.动态内存分配有哪些方式?请简述它们的区别和用途
11.如何使用new和delete操作符进行动态内存分配和释放?
12.C和C++分别怎么动态分配内存?有什么区别?
13.什么是内存泄漏?如何避免内存泄漏?
14.请解释C++中的const指针和指向cons指针的区别。
15.什么是智能指针?请简述智能指针的概含和用途。
16.如何使用std:unique_ptr、std::shared_ptr和std:weak_ptr智能指针?
char szTest1[] = "123";
char szTest2[] = "123";
const char szTest3[] = "123";
const char szTest4[] = "123";
const char *pTest1= "123";
const char *pTest2= "123";
char *pTest3= "123";
char *pTest4= "123";
cout<<(szTest1==szTest2)<<endl; //1 , 输出: 0
cout<<(szTest3==szTest4)<<endl; //2 , 输出: 0
cout<<(pTest1==pTest2)<<endl; //3 ,输出: 1
cout<<(pTest3==pTest4<endl; //4 ,输出: 1
szTest1、szTest2、szTest3、szTest4都是数组,分配新空间,它们有各自的内存空间,这样写szTest1代表的数组的首地址,因此szTest1、szTest2、szTest3、szTest4都不相同;
pTest1、pTest2、pTest3、pTest4都是指针,指定的地址都相同,所以4个都是相同,因此输出是:0? 0? 1? 1
char szTest1[] = "hello world";
const char* pTest1 = "hello world";
int len1 = sizeof(szTest1);
int len2 = strlen(szTest1);
int len3 = sizeof(pTest1);
int len4 = strlen(pTest1);
cout << len1;//输出 12
cout << len2;//输出 11
cout << len3;//输出 8
cout << len4;//输出 11
szTest1是数组,以'\0'结尾,sizeof计算内存占用空间,所以输出12,strlen计算字符串的长度,字符串以'\0'结尾结束,所以输出11;pTest1是个指向const char的指针,指针在x64下固定是8个字节,所以输出是8,strlen(pTest1)是计算pTest1指向的字符串长度,故输出11。
int calcLen(char str[])
{
return (int)(sizeof(str)-1);//无论何时都是返回3
}
当函数传递的是数组指针的时候就自动退化为指针了, 而指针的长度为4,减去1就是3
int m[5] = { 4, 12, 241, 33, 545 };
int *ptr = (int *)(&m + 1);
printf("%d,%d", *(m + 1), *(ptr - 1));
理解指针运算,”+1“就是偏移量的问题:一个类型为T的指针移动,是以sizeof(T)为单位移动的。m+1:在数组首元素地址的基础上,偏移一个sizeof(a[0])单位。因此m+1就代表数组第1个元素,为2;
&m+1:在数组首元素的基础上,偏移一个sizeof(m)单位,&m其实就是一个数组指针,类型为int[5]。因此&m+1实际上是偏移了5个元素的长度,也就是m+5;再看ptr是int类型,因此"ptr-1"就是减去sizeof(int*),即为m[4]=5;
m是数组首地址,也就是m[0]的地址,m+1是数组下一个元素的地址,即m[1];&m是对象的首地址,&m+1是下一个对象的地址,即m[5]
void GetMemory(char *p)
{
p=new char[100];
strcpy(p,"hello world");
}
void main(void)
{
char *str=NULL;
GetMemory(str);
cout<<str;
delete []str;
str=NULL;
}
初看函数逻辑,一切都是那么理所当然。仔细看下,这里的str是指char*的指针,把str指针拷贝了一份传给函数GetMemory,??GetMemory参数中的p和str不一样了,所以在GetMemory中改变p,不会影响到str,main函数中的str还是NULL,如果在GetMemory要返回new出的地址,那参数p必须为引用或双重指针,双重指针代码如下:
void GetMemory(char **p)
{
*p=new char[100];
strcpy(*p,"hello world");
}
void main(void)
{
char *str=NULL;
GetMemory(&str);
cout<<str;
delete []str;
str=NULL;
}
也可以用引用,代码如下:
void GetMemory(char*& p)
{
p=new char[100];
strcpy(p,"hello world");
}
void main(void)
{
char *str=NULL;
GetMemory(str);
cout<<str;
delete []str;
str=NULL;
}
void func()
{
char b;
char *p = &b;
strcpy(p, "hello world");
cout<<p;
}
因为 char类型的a变量只拥有了一字节的空间,但是"hello"拥有6字节的空间(包含最后的'\0'),所以程序出现问题,有可能崩溃,因为strcpy的时候有可能把func的返回地址覆盖了,函数调用返回不到上级函数,出现异常。
int *a[10];
? ?由于符号[]的优先级高于*, 所以a优先于[]结合,a是一个数组,数组的每个元素都是指向int的指针
int (*a)[10];
? ?由于符号()的优先级高于[],所以a优先于*结合,a是一个指针,执行int类型的数组
int (*a)(int);
? ? 首先a是一个指针,去掉a的()部分就是 int (int),这不就是一个函数吗,所以a是一个指向int(int)函数的指针,即函数指针。
int (*a[10])(int);
分解:1) int (int)? 2) a[10]? ? 3) *,所以a是一个大小为10的数组,数组的每个元素都是指针,该指针指向一个函数,该函数有一个整型参数并返回一个整型数。
int ((*a)[10])(int);
分解:1) int (int)? 2) [10]? ? 3) *a,所以a是一个指针,指向大小为10的数组,数组的每个元素都是函数,该函数有一个整型参数并返回一个整型数。
下面看一道比较经典的题目:
class MyTest {
public:
void Test() {
cout << "Test()" << endl;
}
private:
int m_a;
};
int main()
{
MyTest* p = nullptr;
p->Test();
}
上面的代码运行会出现异常吗?答案是不会。
原因分析:1不管是C语言的函数还是C++得成员函数都有一个准确的地址? ?2 在main函数中p调用Test函数的时候,指针p会通过汇编寄存器ecx传给函数Test的入口参数? ?3 进入函数Test后这个ecx就赋值给this指针,可以通过this指针访问MyTest类中的成员变量和成员函数。
上面代码MyTest类中Test函数中,虽然this指针是空的,但是函数里面也没有访问任何成员函数和成员变量,所以不会报错。如果把题目改为如下:
class MyTest {
public:
void Test() {
cout << m_a << endl;
}
private:
int m_a;
};
int main()
{
MyTest* p = nullptr;
p->Test();
}
运行报错,原因是Test函数中的this指针是空的,一旦访问m_a就提示访问内存报错。
如果还有不明白的同学可以去翻翻看<<深入理解C++对象模型>>,这本书把C++对象模型讲的比较深,比较透。
参考
<<深入理解C++对象模型>>