前言
指针作为C语言的重点,在数据结构中也发挥了重要作用,对指针的理解很有助于我们实现各种数据结构。所以我觉得有必要来复习一下指针的内容。本次复习是假设读者已经对指针有了大致的了解,知道取地址符、间接寻找符等运算符的作用,知道指针的运算等。这次复习是对一些细节内容的复习,而且是针对数据结构学习到的内容复习。
一、字符串的指针
字符串实际上就是字符数组,我们这里说字符串的指针,也就是字符数组的指针。
1.1、字符串的两种表示
我们知道字符串有两种表示方式,我们可以用字符数组表示,也可以用字符指针表示,如下:
char *sentence1 = "Do not go gentle into that good night!"; char sentence2[] = "Do not go gentle into that good night!"; 复制代码
上面两句我们都可以正常输出,也可以使用下标运算符[],也可以用间接寻址符*,我们看如下代码:
//直接输出两个字符串 printf("%s\n", sentence1); printf("%s\n", sentence2); //使用下标运算符 printf("%c", sentence1[0]); printf("%c", sentence2[1]); //使用间接寻址符(指针运算符) printf("%c", *(sentence1+2)); printf("%c", *(sentence2+3)); 复制代码
输出结果如下:
Do not go gentle into that good night! Do not to gentle into that good night! Do n 复制代码
可以看出每条代码都顺利执行了,那么这两种方式有区别吗?
1.2、字符指针和字符数组的区别
实际上,我们使用指针方式定义的是一个字符串常量,是不可修改的,而用数组定义的是字符串变量,我们分别用sentence1和sentence2测试如下代码:
//修改字符串内容 sentence1[2] = '#'; //输出修改后的内容 printf("sentence1 : %s\n", sentence1); 复制代码
其中sentence1执行时异常退出了,而sentence2执行结果如下:
sentence2 : Do#not to gentle into that good night! 复制代码
可以看到字符串修改成功了。
二、动态分配内存的函数
在我们实现数据结构的时候,经常需要动态的分配内存,动态分配内存的方法在malloc.h库中,我们看看下面几个方法。
方法名称 | 函数原型 | 方法作用 |
malloc | void *malloc(unsigned long size); | 向系统申请size大小的内存,返回一个无类型指针 |
calloc | void *calloc(unsigned int n , unsigned int size); | 向系统申请n*size大小的内存,返回一个无类型指针 |
realloc | void* realloc(void* pointer, unsigned int new_size); | 对p指向的内存进行扩展,返回一个无类型指针 |
free | void free(void *p); | 释放p指向的内存 |
在具体说这些函数之前,我们再来温习一下堆栈。
2.1、堆栈
我们先看看什么是堆栈:
栈:由系统自动分配,速度较快。但程序员是无法控制的。
堆:是由程序员分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
像我们平时写代码,如下语句:
int a = 10; char ch = 'a'; float f = 1.4; 复制代码
上面这些,都是申请在栈中的变量。而什么上面讲到的动态分配内存函数分配的,则是分配在堆中的变量。
2.2、malloc函数和free函数
使用该函数时,传入一个无符整形变量size,该函数将向系统堆存储区申请size大小的内存,并返回该内存区的首地址的无类型指针。当申请失败(内存不足)时,返回NULL,我们看如下代码:
int *p = malloc(sizeof(int)*5); 复制代码
我们分配了5个int大小的内存,返回的为无类型指针,很多教程上会将它强转为需要的类型,实际上是不需要的(如果是考试还是需要强转)。
因为是在堆中申请的内存,所以需要我们手动释放:
free(p) 复制代码
2.3、calloc函数和realloc函数
我们先看看calloc,如下代码:
int *p = calloc(5, sizeof(int)); 复制代码
用法上和malloc有异曲同工之处,我们也不过多的讲解了。
然后是我们的realloc函数,这个是用法比较丰富的一个函数,在使用这个函数时,我们需要事先分配一块内存:
int *p = malloc(sizeof(int)*5); 复制代码
然后我们对这块原有的内存进行扩展:
p = realloc(p, sizeof(int)*10); 复制代码
该函数会返回一个无类型的指针,如果p后连续的地址充足,则返回原来的p,否则分配一块新地址。我们看如下代码:
#include <stdio.h> #include <malloc.h> int main(){ int *p = malloc(sizeof(int)*5); //刚创建时,输出地址 printf("before realloc p = %p\n", p); //将p指向的内存区域扩展到sizeof(int)*10 p = realloc(p, sizeof(int)*10); //第一次扩展后输出地址 printf("after realloc 10 p = %p\n", p); //将p指向的内存区域扩展到sizeof(int)*1000 p = realloc(p, sizeof(int)*1000); //第二次扩展后输出地址 printf("after realloc 1000 p = %p\n", p); //释放内存 free(p); return 0; } 复制代码
输出结果如下:
before realloc p = 00AB1658 after realloc 10 p = 00AB1658 after realloc 1000 p = 00AB3E38 复制代码
可以看到,第一次扩展因为内存充足,所以在原来的地址扩展,第二次内存不足,所以新申请了一块内存。
如果内存不足,扩展新地址的过程中,原地址中的内容也会复制。
除此之外,realloc函数还可以用来缩小:
p = realloc(p, sizeof(int)) 复制代码
我们还可以用realloc来释放内存:
realloc(p, 0); p = NULL; 复制代码
三、指针的初始化
指针的初始化方式是多样的,我们可以使用动态分配内存的方式初始化,也可以使用取地址符,还可以使用直接赋值的方式,我们一个一个来了解一下。
3.1、使用取地址符
这是我们常使用的一种方式,我们先定义好一个变量,并为它初始化,然后我们使用取地址符将这个变量的地址赋值给一个指针变量。人们看如下代码:
int a = 10; int *p = &a; 复制代码
在我们最初学习指针时,我们就学了这种方式,这个方法也是比较常用的,就不再细说了。
3.2、直接赋值
除了用取地址符,我们可以用直接赋值的方式,即我们已知一个地址,将该地址赋值给指针变量。
int *p = 1001; 复制代码
这种方式一般不会使用,因为我们通常情况下不需要自己指定特定的地址,让电脑直接分配就可以了。
3.3、用指针赋值
指针变量有基本类型变量的大部分操作,赋值操作当然也是有的,我们先创建一个指针变量并初始化,然后将该指针赋值给另一个指针变量。
int x = 10; int *p1 = &x; int *p2 = p1; 复制代码
这种方式我们也时常会使用到,但是对于这种方式,我们更多的是认为这是指针的**=运算**。
3.4、动态初始化
所谓动态初始化就是我们上面说到的,使用动态分配内存的函数,手动的分配内存。代码如下:
int *p = malloc(sizeof(int)); 复制代码
这种方式的特点就是分配的内存不会自己销毁,需要程序员手动销毁。
到此我们就将指针的指针简单复习了一遍,更多的内容需要读者自己在之前的学习中寻找。