指针是 C 语言的灵魂,现在对于指针的掌握肯定是不透彻的,然学习是一个迭代的过程,姑且写出目前自己的理解。
指针的概念
|
|
先来看这样一段代码
- p 是一个指针变量,*p 是指针指向的内存空间的保存的值。
- &a 表示取变量 a 所在内存的地址,p = &a 表示将内存 a 所在的地址值赋值给 p,相当于指针 p 指向 a 所在的内存。
- 接下来分别打印 p 和 *p 来验证上面的说法
- 最后我们释放内存空间
明确几个概念:
- 指针是一个变量
- 注意区分指针变量和它指向的内存块
- 改变指针变量的值不会影响指针指向的内存区域
- 改变指针指向的内存区域也不会改变指针变量的值
内存四区
先来看这样一段代码
|
|
char *p 是一个 char 类型的指针变量,看代码的意思是希望通过 get_str() 方法给指针 p 赋值。如果你运行一下这段代码会发现它是会报错的,至于为什么会报错就要分析一下内存四区了。
区域 | 作用 |
---|---|
栈区(stack) | 由编译器自动分配释放,存放函数的参数值,局部变量值等。 |
堆区(heap) | 一般由程序员分配释放(动态内存申请与释放),若程序员不释放,程序结束时可能由操作系统回收。 |
全局区(静态区) | 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未被初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。 |
程序代码区 | 存放函数体的二进制代码。 |
现在通过内存四区的理论来分析上面这一段代码。
- 声明了一个名为 buf 的变量
- 调用 get_str()
- 声明一个数组 str ,它是一个局部变量所以再栈区
- 字符串 “abcdef…” 是字符串常量,所以再全局区(静态区)
- str = “abcdef” 会将全局区的字符串常量拷贝到 str 数组内存中
- 紧接着 get_str() 调用结束,与之相关的内存就会被销毁
- strcpy() 希望将 get_str() 返回的字符串拷贝给 buf,但是因为这一块内存已经被销毁了,就会报错
注:有时候以上的操作可能会成功,这可能是因为没有及时销毁,可以用我上面注释掉的指针操作的代码做练习,用内存四区的方式分析,赋值是一定不会成功的,原理相同。
正确的赋值方式
我们可以使用 malloc() 将变量保存在堆区,堆区的内存在函数执行完之后不会立即销毁,而是由我们自己销毁,或者再程序运行完之后由操作系统来销毁。
|
|
使用指针的几个注意事项
这一小节属于使用指针过程中遇到的一些问题,以及如何避免,有个故事说:人如果知道自己会死在哪里,就死也不要去那里就行了。使用指针也一样,指针使用会遇到很多的坑,知道哪里有坑就要尽可能避免。
修改内存时要保证内存可写
|
|
修改 p[2] 会报错,因为 abcdef 是字符串常量,保存在全局区(静态区),所以它是不能被修改的。使用指针的第一个事项就是修改内存的时候保证内存可写。
指针的步长
指针的步长由指针指向的内存类型决定,比如说 int p 步长就是 4,chat p 步长就是 1。步长的意思就是每次 p++ 操作之后,地址移动的距离。可以分别打印 p 和 p++ 的值看一看就知道了。
由此还想讲一个问题就是如何计算一个动态数组的大小。比如 int a[] = {1, 2, 3, 4},可以使用 sizeof(a)/sizeof(a[0])
的方式计算数组大小。
不允许 NULL 或者未知非法地址拷贝内存
|
|
strcpy() 执行的操作时将字符串拷贝给指针 p 所指向的内存,此时指针 p 指向的内存是 NULL,就是说没有执行任何内存,字符串也就没地方拷贝,自然会发生错误。
如果我们让char* p = 0x11;
任意赋一个值,也会报错,向未知非法地址拷贝也是不被允许的。 正确的做法就是将 p 指向一个已经分配好内存的指针,比如 char *q = “fdsafas” 。
所以当我们给一个使用 malloc() 分配内存之后应该习惯性地加一个判断,比如int *p = (int *)malloc(sizeof(int));
之后应该进行判断if(p == NULL)
如果为空则说明内存分配失败,就应该对异常进行处理。一直对应的,释放内存是有个也应该讲指针赋值为空free(p); p = NULL;
。
通过指针间接赋值
我们定义一个函数会有返回值,返回值可能是一个结果、可能表示一种状态。比如说int findMax(int *a, int n);
在这个函数中我们出入一个数组,然后返回这个数组中最大的值。
那现在如果我想返回这个数组的最大值、最小值该怎么办?
|
|
定义一个函数int fun(int *a, int n, int *max, int *min);
- 返回值表示成功或者失败
- a 是数组指针
- n 是数组大小
- max 和 int 是 int 类型的指针
关键就在于 max 和 int 这两个指针,它们指向的两个内存分别保存数组的最大和最小值,而这两个指针是由主函数传入的,此时就完成你了最大值和最小值传递。
通过指针间接赋值是指针很强大的一个功能,我们可以看到很多函数都是这样定义的。