例 1: 程序测试后会有什么结果?
#include<iostream>
#include<cstring>
using namespace std;
void getMemory(char*p, int num)
{
p = (char*)malloc(sizeof(char) * num);
}
int main(int argc, const char* argv[])
{
char *str = NULL;
getMemory(str, 100);
strcpy(str, "hello");
return 0;
}
问题出现在getMemory
里,编译器总是为函数的每个参数制作一个临时副本。在本题中,p 为 str 的拷贝,p 申请了一个新的内存空间,但是并没有影响到 str,str 还是 NULL,再调用 strcpy(), 则会使代码崩溃,并且 p 申请的内存也一直没用释放,造成内存泄露。
正确的方法是把往 getMemory 内传入 str 的地址。
#include<iostream>
#include<cstring>
using namespace std;
void getMemory(char**p, int num)
{
*p = (char*)malloc(sizeof(char) * num);
}
int main(int argc, const char* argv[])
{
char *str = NULL;
getMemory(&str, 100);
strcpy(str, "hello");
cout << str << endl;
return 0;
}
不过这样写有些麻烦,我们可以直接把申请好的内存返回。
#include<iostream>
#include<cstring>
using namespace std;
char* getMemory(int num)
{
return (char*)malloc(sizeof(char) * num);
}
int main(int argc, const char* argv[])
{
char *str = NULL;
str = getMemory(100);
strcpy(str, "hello");
cout << str << endl;
return 0;
}
例 2: 这个函数有什么问题?
char* strA()
{
char str[] = "hello world";
return str;
}
str 里存的地址是函数 strA 栈帧里"hello world"
的首地址,函数调用完成,临时空间被重置。
如果要获得正确的函数,可以这样写:
char* strA()
{
char *str = "hello world";
return str;
}
首先要搞清楚char str[]
, char* str
,
char str[]
分配的是一个局部数组,
char* str
分配的是一个指针遍历,
局部数组是局部变量,它对应的是内存中的栈。指针变量是全局变量,它对应的是内存中的全局区域。
不过上述代码只能 c 语言这么写,c++不允许
ISO C++ forbids converting a string constant to ‘char*’
可以这么写:
char* strA()
{
static char str[] = "hello world";
return str;
}
例 3: 下面代码的输出结果是什么?
#include<iostream>
using namespace std;
class A
{
public:
A() { m_a = 1; m_b = 2; }
~A(){}
void fun() { printf("%d%d", m_a, m_b);}
private:
int m_a;
int m_b;
};
class B
{
public:
B() {m_c = 3;}
~B();
void fun() {printf("%d", m_c);}
private:
int m_c;
};
int main(int argc, const char* argv[])
{
A a;
B *pb = (B*)(&a);
pb->fun();
return 0;
}
这道题的目的就是考察你对内存偏移的理解,
B* pb = (B*)(&a);
, 这是一个野蛮的转换,强制把 a 地址内容看成一个 B 类的对象,pb 指向的是 a 类的内存空间, 把 a 类空间按照 B 类的结构来解读。
例 1: 找出下列程序的错误
#include<iostream>
using namespace std;
int Max(int x, int y)
{
return x > y ? x : y;
}
int main(int argc, char const *argv[])
{
int *p = &Max;
cout << p(2, 1) << endl;
return 0;
}
这道程序提存在着函数指针的错误使用问题,正确的写法为: int (*p)(int, int) = &Max;
int *p
p 是 int 型的指针
int *p(int, int)
, p 是一个函数,返回值为int*
int (*p)(int, int)
, p 是一个指针,指向函数的地址,函数的返回值为 int
例 2: 下面的数据声明都代表什么?
float(**def)[10];
double*(*gh)[10];
double(*f[10])();
int*((*b)[10]);
long(*fun)(int)
int(*(*F)(int, int))(int)
答案如下:
float(**def)[10]; // def 是二级指针,指向一级指针,一级指针指向数组,数组的大小为 10,数组元素类型为 float
double*(*gh)[10]; // gh 是一级指针,指向一个数组,数组大小为 10,数组元素的类型为 double*
double(*f[10])(); // f 是一个数组,数组大小为 10,数组的元素类型为指针,指针指向的类型为 double() 的函数
int*((*b)[10]); // b 是一个指针,指向一个数组,数组的大小为 10,数组元素的类型为 int*
long(*fun)(int) // fun 是一个函数指针,指向 long(int) 型的函数
int(*(*F)(int, int))(int) // F 是一个指针,指向一个函数,函数的参数为(int, int), 函数的返回值是一个指针,指向一个函数,函数的参数为(int), 函数的返回值为 int
例 1: 以下程序的输出是什么?
#include<iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int v[2][10] = {
{1,2,3,4,5,6,7,8,9,10},
{11,12,13,14,15,16,17,18,19,20},
};
int (*a)[10] = v; // 数组指针是一个二级指针
cout << a << endl; // a 是一个指针,指向 {1,2,3,4,5,6,7,8,9,10}
cout << *a << endl; // *a 也是一个指针,指向 1 的地址
cout << **a << endl; // **a 取 1 的值
cout << a + 1 << endl; // 指针向后偏移一个位置,这个位置的长度为指针所指容量的大小,偏移后指向 {11,12,13,14,15,16,17,18,19,20}
cout << *(a + 1) << endl; // 和*a 的原理是一样的,指向 11 的地址
cout << **(a + 1) << endl; // 取 11 的值
cout << *a + 1 << endl; // *a 指向 1 的地址,*a + 1, 指针向后偏移一个位置,*a 指向的是 int 型的数据 m, 向后偏移 sizeof(int),指向 2
cout << *(*a+1) << endl; // 取 2
return 0;
}
例 2: 用变量 a 给出下面的定义
答案:
// 1
int a;
// 2
int *a;
// 3
int **a;
// 4
int a[10];
// 5
int *a[10];
// 6
int (*a)[10];
// 7
int (*a)(int);
// 8
int (*a[10])(int);
例 3: 写出如下程序片段的输出
int a[] = {1,2,3,4,5};
int *ptr = (int*)(&a + 1);
printf("%d %d", *(a+1), *(ptr - 1));
答案:
#include<iostream>
#include<cstdio>
using namespace std;
int main(int argc, char const *argv[])
{
int a[] = {1,2,3,4,5};
int *ptr = (int*)(&a + 1);
cout << a << endl; // 数组名的一个指向数组元素的常量指针
cout << &a << endl; // &a 并不是一个指向常量指针的指针,而是一个指向整个数组的指针
// 以下两行验证以上的观点
cout << a + 1 << endl;
cout << &a + 1 << endl;
// 所以 a + 1 指向 2,*(a+1) 为 2
// &a + 1 应该指向 5 的下一个元素,ptr - 1 指向 5
printf("%d %d", *(a+1), *(ptr - 1));
return 0;
}
迷途指针: 指针指向一个内存,这个内存会回收了,但是没有对这个指针做处理,没有将指针设为空
野指针: 声明了一个指针,没有将指针初始化。
例 1: 下面的程序输出结果是什么?
#include<iostream>
using namespace std;
int main(int argc, char const *argv[])
{
char s1[] = "hello";
char s2[] = "the";
char s3[] = "world";
char* a[] = {s1, s2, s3};
char **pa = a;
pa++;
cout << *pa << endl;
return 0;
}
a 是一个常量指针,指向数组的首地址,pa++, 向后挪一个指针大小,指向 s2, 输出 "the"
句柄是一个整数,是操作系统在内存中维护的一个对象,内存物理地址列表的整数索引,因为操作系统在内存管理时经常会将当前空闲对象的内存释放掉,当需要访问时再重新提交到物理内存,所以对象的物理地址是变化的,不允许程序直接通过物理地址来访问对象。 程序将想访问的对象的句柄传递给系统,系统根据句柄检索自己维护的对象列表就能知道程序想访问的对象及其物理地址了。
句柄是一种指向指针的指针。 我们知道,所谓指针是一种内存地址,应用程序启动后,组成这个程序的各对象是驻留在内存的。如果简单的理解,似乎我们只要知道内存的首地址,那么就可以随时用这个地址访问对象。 但是,如果真的这样认为,那么就大错特错了,我们知道,操作系统是一个以虚拟内存为基础的,存在换页现象,对象呗移动意味着它的地址变化了,如果地址总是变化,我们该怎么寻找对象呢? 为了解决这个问题,操作系统为各应用程序腾出一些内存地址,用来专门登记各应用对象在内存中的地址变化,而登记的地址是不变的,操作系统移动对象后,将对象的地址告诉给句柄,通过句柄就能知道对象具体的位置了。
句柄--> 登记对象地址的地址 --> 对象的地址 --> 对象
程序每次重新启动,系统不保证分配给这个程序的句柄还是原来的句柄,就好比去电影院每次卖给我们的都不是同一个座位。
关于 this 指针,有这样一段描述: 当你进入一个房子后,你可以看见桌子,椅子,等,但是你看不到全貌了
对于一个类的实例来说,你可以看到成员函数,成员变量,但是实例本身呢? this 指针就是这样一个指针,时时刻刻指向实例本身
a.func(10);
会被编译器编译成 A::func(&a, 10);
, 看起来和静态函数没区别,不过,区别还是有的。 编译器通常会对 this 指针做一些优化,this 指针的传递效率比较高,如 VC 通常是通过 ecx 寄存器传递。
this 指针只有在成员函数中才有定义。 因此,你获得一个对象后,也不能通过对象使用 this 指针,只有在成员函数内才有 this
1
wutiantong 2021-02-25 00:40:53 +08:00 7
所以中文编程技术社区一直存在的一个严重问题是,无论是在书本上还是论坛博客上,到处都可能充斥着似是而非,混淆概念,不够精确的表达。就以这篇文章来说:
1. 指针可以指向空。 2. 指针应该总是要被测试,防止其为空 3. 编译器总是为函数的每个参数制作一个临时副本。 4. p 申请了一个新的内存空间。。。并且 p 申请的内存。。。 5. 我们可以直接把申请好的内存返回。 6. str 里存的地址是函数 strA 栈帧里"hello world"的首地址 7. (这里整段都是错误的,包括代码) char str[] 分配的是一个局部数组,char* str 分配的是一个指针遍历 。。。 指针变量是全局变量,它对应的是内存中的全局区域。 8. (又在讲错误的东西)强制把 a 地址内容看成一个 B 类的对象,pb 指向的是 a 类的内存空间, 把 a 类空间按照 B 类的结构来解读。 9. *a 也是一个指针,指向 1 的地址 10. 数组名的一个指向数组元素的常量指针。。。&a 并不是一个指向常量指针的指针 |
2
bfdh 2021-02-25 10:55:47 +08:00
指针真的是越解释越乱,指针就是一个地址而已,那些所谓的指针类型不过是解读指针指向内容的方法而已,都不影响指针的本质。
|
4
yiouejv OP 我又不是培训机构,自己学习分享而已。不卖课哈
|