指针
OVREVIEW
一、指针变量
1.定义方式:
方式:基类型 *指针变量
,指针变量是基本数据类型派生出来的类型,其不能离开基本类型而独立存在。
- 指针类型表示:指向整型数据的指针类型表示为
int*
,读作指向int的指针 or int指针 - 在定义指针变量时必须指定基类型,不同类型的数据在内存中所占的字节数和存放方式是不同的。只有知道该数据类型,才能按存储单元长度和数据的存储形式正确的取出该数据。
2.引用方式:
&取地址符,*间接访问运算符
- 给指针变量赋值:
p = &a;
- 引用指针变量指向的变量:
printf("%d", *p);
- 引用指针变量的值:
printf("%o", p);
注:C语言中实参变量和形参变量之间的数据传递是单向的值传递方式,用指针变量作函数参数同样要遵循这一规则。
- 不可改变实参指针变量值
- 但可改变实参指针变量所指向变量的值
二、指针引用数组
1.数组元素指针运算
所谓数组元素指针就是数组元素地址,
引用数组元素可以用下标法,也可以用指针法(通过指向数组元素的指针找到所需的元素,占用内存少、运行速度快)。
当指针指向数组元素时,允许对指针(地址)进行加减运算。
- 如果指针变量指向数组中一个元素,则p+1指向该元素的下一个元素,p-1指向该元素的上一个元素。
- 如果指针变量p1和p2都指向同一个数组,执行p1-p2的结果是
(p1-p2)/sizeof(arrElem)
,即所指元素之间相差元素的个数。 - 两个地址不能相加,其结果是无实际意义的。
2.指针引用数组元素
引用数组元素的方法有下标法arr[i]
与指针法*(arr + i)
:
#include<stdio.h>
.
int main() {
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int i = 0; i < 10; ++i) printf("%d ", a[i]);//(1)下标法引用数组元素
printf("\n");
for (int i = 0; i < 10; ++i) printf("%d ", *(a + i));//(2)数组名计算元素地址 间接访问该元素
printf("\n");
for (int *p = a; p < (a + 10); ++p) printf("%d ", *p);//(3)指针变量引用数组元素
printf("\n");
return 0;
}
- 方法(1)和方法(2)的执行效率相同,编译器将
a[i]
转换为*(a+i)
再进行处理/计算元素地址,这两种方式寻找数组元素费时较多。 - 方法(3)速度较快,用指针变量直接指向元素/不必每次都重新计算元素地址,这种有规律的改变地址值能有效提高执行效率。
- 对于方法(3)的执行指针不能直接用数组名arr替代(需要额外定义指针类型),因为数组名是一个指针型常量,其值固定不变。
注:利用指针引用数组元素,比较灵活有不少技巧。
*p++
:由于++
与*
运算符同优先级,结合方向为自右向左,因此其等价于*(p++)
*(p++)
与*(++p)
:作用不同,前者是先取*p
然后+1,而后者是先+1然后取*p
3.数组名作函数参数
用数组名作为函数实参时,
函数对应的形参可以是指针变量int *arr
,也可以是形参数组int arr[]
,这是因为C语言中下标法和指针法都可以访问到一个数组。
即如果有一个数组arr,则arr[i]
与*(arr + i)
无条件等价。
注:如果用指针变量
int *arr
作为实参,必须先使指针变量有确定值指向一个已定义的对象,否则报错。
三、指针引用字符串
通过指针引用字符串,可以使字符串的使用更加灵活方便。
1.字符串引用方式
方式1:字符数组arr[i]
、printf("%s", arr)
方式2:字符指针char *string = "I love china!"
、printf("%s", string)
-
对字符指针string初始化,实际上是把字符串的第1个元素的地址赋值给string指针,使string指向字符串第一个字符。
-
%s是输出字符串时所用的格式符,系统会输出string指向的第一个字符,然后自动使string+1输出下一个字符直到遇到字符串结束标志
\0
为止。 -
在内存中字符串的最后被自动加上
\0
, -
对于数值型数组的元素值只能逐个输出
程序阅读:字符串拷贝函数的进化过程
void copy_string(char *src, char *dest) {
while((*dest = *src) != '\0') {
dest++;
src++;
}
}
void copy_string(char *src, char *dest) {
while((*dest++ = *src++) != '\0');
}
void copy_string(char *src, char *dest) {
while((*dest++ = *src++));
}
2.字符指针和字符数组比较
用字符数组char arr[];
与字符指针char *arr;
都能实现字符串的存储与运输,但是其二者之间是有区别的:
-
字符指针变量中存放的是地址,而字符数组是由若干个元素组成,每个元素中存储一个字符。
-
编译时对字符指针变量只分配一个存储单元,而为字符数组分配若干存储单元,以存放各元素的值。
-
可以对字符指针赋值,但不能对字符数组名赋值(常量
const char *
)。 -
字符数组中各个元素的值是可以进行修改的,但字符指针指向的字符串不能被赋值(除非直接更换指针指向)
char a[] = "House"; char *b = "House"; a[2] = 'r';//合法 b[2] = 'r';//非法
-
若字符指针指向字符,则其对字符数组元素的引用也可以使用下标法和地址法,否则其引用是没有意义的。
注:如果定义了一个字符指针应及时把一个字符变量的地址赋给它,使其指向一个字符型数据。如果未对其赋予一个地址值,此时向其所指对象输入数据,可能会造成严重后果(其所指对象是不可预料的,可能是内存中空白的存储区,也可能是已经存放指令或数据的重要内存段!)。
四、动态内存分配
1.内存动态分配
- 全局变量是分配在内存中的静态存储区的
- 局部变量/非静态(包括形参)是分配在内存中的动态存储区的,栈stack
- 临时数据是是存放在内存动态分配区域,堆heap
内存动态分配区域用于存放一些临时数据,这些数据不必在程序的声明部分定义,不必等到函数结束时才释放,而是随时开辟随时释放。
可以根据需要向系统申请所需大小的空间,由于未在声明部分定义他们为变量或数组,因此不能通过变量或数组名来引用这些数据,只能通过指针来引用。
2.malloc
其作用是在内存的动态存储区中分配一个长度为size的连续空间,
- 原型:
void *malloc(unsigned int size);
- size:无符号整型(不允许为负数)
- 返回值为所分配区域的第一个字节的地址
注:返回指针的基类型为void,即不指向任何类型的数据只提供一个地址,若空开辟失败则返回空指针NULL。
3.calloc
其作用是在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,
用calloc函数可以为一维数组开辟动态存储空间,n为数组元素的个数,每个元素长度为size,这就是动态数组。
- 原型:
void *calloc(unsigned n, unsigned size);
- 返回值为所分配区域的第一个字节的地址,如果开辟不成功则返回空指针NULL。
4.realloc
如果已经使用malloc或calloc函数获得了动态空间,向改变其大小则可以使用realloc函数进行重新分配。
使用realloc将p所指向的动态空间大小改变为size,p的值不变。
- 原型:
void *realloc(void *p, unsigned int size);
- 如果分配不成功则返回NULL
5.free
其作用是释放指针p所指向的动态空间,使这部分空间能够被其他变量使用,
- 原型:
void free(void *p);
- p应是最近一次调用calloc或malloc函数时所得到的函数返回值(开辟的空间的首地址)