C语言学习(6)—— 指针
一、指针
(1)指针是内存的地址;指针变量是保存了内存地址的变量。
(2)在声明指针变量时,如果没有确切的地址赋值,则声明为空指针:int *ptr = NULL。
(2)获取变量的地址用 &,比如: int num = 10,获取num的地址:&num
(3)指针变量存的是一个地址,这个地址指向的空间存的才是值,比如: int *ptr = # ptr 就是指向int类型的指针变量,即 ptr 是 int* 类型,ptr存的是num的地址,该地址存的是num的值。
(4)获取指针变量所指向的值使用 *,比如: int *ptr = &num,使用*ptr获取ptr指向的值,即num的值。
(5)指针是一个变量,其值为另一个变量的地址。就像其他变量或常量一样,在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为: ① int *ip:一个整型的指针;(2) double *dp:一个 double 型的指针;③ float *fp:一个浮点型的指针;(4)char *ch:一个字符型的指针。
#include<stdio.h>
int main(){
int num = 2;
// 获取变量的值
printf("num的地址为: %p \n", &num); // num的地址为: 000000000061FE1C
// int* 表示类型为 整数指针类型
// ptr是一个 int* 类型,指向了int类型变量 num 的地址
int *ptr = #
// 获取指针变量的地址(&ptr):指针变量本身也有地址
printf("ptr的地址为: %p \n", &ptr); // ptr的地址为: 000000000061FE10
// 获取指针变量存储的内容(ptr):指针变量存储的是 num 的地址
printf("ptr存储的内容为: %p \n", ptr); // ptr存储的内容为: 000000000061FE1C
// 获取指针变量指向的值(*ptr):指针变量指向的值是 num 的值
printf("ptr指向的值为: %d \n", *ptr); // ptr指向的值为: 2
return 0;
}
二、指针的算数运算
1. 自增和自减
#include<stdio.h>
int main(){
int arr[] = {10, 12, 15};
int *ptr = arr; // ptr是一个 int* 类型,指向了数组的首地址,即arr[0]的地址
// 通过指针来获取数组中每一个元素的值
for(int i = 0; i < 3; i++){
printf("arr[%d]的地址: %p \n", i, ptr); // ptr 指向数组元素的地址
printf("arr[%d] = %d \n", i, *ptr); // *ptr 是 ptr 指向地址对应的值
ptr++; // ptr = ptr + 1(1个 int 长度的字节数),ptr指向了数组的下一个地址
}
return 0;
}
#include<stdio.h>
int main(){
int arr[] = {10, 12, 15};
int *ptr = &arr[2]; // ptr是一个 int* 类型,指向了数组 arr[2] 的地址
for(int i = 2; i >= 0; i--){
printf("arr[%d]的地址: %p \n", i, ptr);
printf("arr[%d] = %d \n", i, *ptr);
ptr--; // ptr = ptr - 1(1个 int 长度的字节数),ptr指向了数组的前一个地址
}
return 0;
}
2. 加法和减法
#include<stdio.h>
int main(){
int arr[] = {10, 12, 15};
int *ptr = arr; // ptr是一个 int* 类型,指向了数组的首地址,即arr[0]的地址
// 通过指针来获取数组元素的值
ptr += 2; // ptr = ptr + 2(2个int长度的字节数),ptr指向了当前地址 +2 后的地址
printf("%d \n", *ptr); // 15
ptr -= 1;
printf("%d \n", *ptr); // 12
return 0;
}
三、指针函数
当函数的形参类型是指针类型时,需要传递指针(变量的地址 或 数组)。
1. 函数形参是指针
#include<stdio.h>
void add(int *num){ // num 等于 变量 的地址
(*num)++; // *num 指向 变量 的值
}
int main(){
int n = 3;
int *ptr = &n;
add(&n);
printf("n = %d \n", n); // n = 4
add(ptr);
printf("n = %d \n", n); // n = 5
return 0;
}
// 数组
#include<stdio.h>
// 通过下标的方式:该方式不会使 arr 的值发生变化
int getSum1(int *arr, int size){ // arr 指向 数组 的首地址
int sum = 0;
for(int i = 0; i < size; i++){
sum += arr[i];
}
return sum;
}
// 通过自增修改地址的方式:该方式会使 arr 的值发生变化
int getSum2(int *arr, int size){ // arr 指向 数组 的首地址
int sum = 0;
for(int i = 0; i < size; i++){
sum += *arr; // *arr 为当前地址的值
arr++; // arr++ 指向 数组的下一个地址
}
return sum;
}
// 通过移动地址的方式:该方式不会使 arr 的值发生变化
int getSum3(int *arr, int size){ // arr 指向 数组 的首地址
int sum = 0;
for(int i = 0; i < size; i++){
sum += *(arr + i); // *(arr + i) 为 arr[i] 地址的值
}
return sum;
}
int main(){
int nums[] = {1, 2, 3, 4};
int sum;
sum = getSum1(nums, 4);
printf("sum = %d \n", sum); // sum = 10
sum = getSum2(nums, 4);
printf("sum = %d \n", sum); // sum = 10
sum = getSum3(nums, 4);
printf("sum = %d \n", sum); // sum = 10
return 0;
}
2. 函数返回值是指针
指针函数:函数的返回值是一个指针(地址)
#include<stdio.h>
int *add(int *arr1, int len, int *arr){
for(int i = 0; i < len; i++){
arr[i] = arr1[i] + 2;
}
return arr;
}
int main(){
int nums1[] = {1, 2, 3};
int *nums;
int *result;
result = add(nums1, 3, nums);
for(int i = 0; i < 3; i++){
printf("%d ", result[i]); // 3 4 5
}
return 0;
}
#include<stdio.h>
#include<string.h>
char *getLong(char *string1, char *string2){
if(strlen(string1) > strlen(string2)){
return string1;
}else{
return string2;
}
}
int main(){
char str1[] = "ABCD";
char str2[] = "EF";
char *str;
str = getLong(str1, str2);
printf("%s", str); // ABCD
return 0;
}
(1)用指针作为函数返回值时,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形参,函数返回的指针不能指向这些数据;(2)函数运行结束后会销毁所有的局部数据,这里所谓的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存;
// 错误示例
#include<stdio.h>
int *getSum(int *arr, int size){
int sum = 0; // 局部变量,在getSum返回时就会销毁
for(int i = 0; i < size; i++){
sum += arr[i];
}
return ∑ // 错误:不能返回局部变量的地址,因为该地址在调用完函数时会销毁
}
int main(){
int nums[] = {1, 2, 3, 4};
int *ptr;
int sum;
ptr = getSum(nums, 4);
sum = *ptr;
printf("sum = %d \n", sum);
return 0;
}
(3)C语言不支持在调用函数时返回局部变量的地址,如果确实有这样的需求,需要定义局部变量为 static变量。
#include<stdio.h>
int *getSum(int *arr, int size){
static int sum = 0; // 静态变量存储在静态存储区,该区域的不会由于函数执行情况被回收
for(int i = 0; i < size; i++){
sum += arr[i];
}
return ∑
}
int main(){
int nums[] = {1, 2, 3, 4};
int *ptr;
int sum;
ptr = getSum(nums, 4);
sum = *ptr;
printf("sum = %d \n", sum);
return 0;
}
#include<stdio.h>
int *add(int *arr1){
static int arr[3];
for(int i = 0; i < 3; i++){
arr[i] = arr1[i] + 2;
}
return arr;
}
int main(){
int nums1[] = {1, 2, 3};
int *result;
result = add(nums1);
for(int i = 0; i < 3; i++){
printf("%d ", result[i]); // 3 4 5
}
return 0;
}
四、函数指针
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。
函数指针定义:函数返回值类型 (*指针名称) (函数参数列表);
注意:(1)参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称;(2)第一个括号不能省略,如果写作函数返回值类型 *指针名称 (函数参数列表)就成了函数原型。
#include<stdio.h>
int getMax(int x, int y){
return x > y ? x : y;
}
int getMin(int x, int y){
return x > y ? y : x;
}
int main(){
int a = 2;
int b = 3;
int maxValue;
int minValue;
// ptrFun是函数指针的名称,int表示函数返回值为int,(int, int)表示该函数指针指向的函数形参是两个int
int (*ptrFun)(int num1, int num2);
ptrFun = getMax; // ptrFun指向函数getMax的地址
maxValue = ptrFun(a, b); // 调用函数getMax
printf("%d", maxValue); // 3
ptrFun = getMin; // ptrFun指向函数getMin的地址
minValue = ptrFun(a, b); // 调用getMin
printf("%d", minValue); // 2
return 0;
}
五、回调函数(函数挂钩)
函数指针变量可以作为某个函数的形参,回调函数就是一个通过函数指针调用的函数。
将自己编写的钩子函数挂在已经声明的函数指针上
#include<stdio.h>
int getMax(int x, int y){
return x > y ? x : y;
}
int getMin(int x, int y){
return x > y ? y : x;
}
// ptrFun是回调函数,进行函数挂钩
int function(int (*ptrFun)(int num1, int num2), int x, int y){
int result = ptrFun(x, y);
return result;
}
int main(){
int a = 2;
int b = 3;
int maxValue;
int minValue;
maxValue = function(getMax, a, b); // 挂getMax的钩子函数,调用getMax
printf("%d \n", maxValue); // 3
minValue = function(getMin, a, b); // 挂getMin的钩子函数,调用getMin
printf("%d \n", minValue); // 2
return 0;
}
六、多重指针
多重指针:定义一个指向指针的指针,第一个指针包含了第二个指针的地址,第二个指针指向实际值的地址。
数据类型 **指针名
#include<stdio.h>
int main(){
int num = 2;
int *ptr = # // 一级指针,指向 num值 的地址
int **pptr = &ptr; // 二级指针,指向 ptr 的地址
printf("num = %d, num的地址: %p \n", num, &num);
printf("num = %d, num的地址: %p, ptr的地址: %p \n", *ptr, ptr, &ptr);
printf("num = %d, num的地址: %p, ptr的地址: %p, pptr的地址: %p \n", **pptr, *pptr, pptr, &pptr);
}
七、指针数组
指针数组:数组中的元素 是 基本数据类型 的地址(指针)
数据类型 *指针数组名[大小]
int *ptr[3]:ptr是一个指针数组,包含3个整数指针,其中每个元素都是一个指向int类型值 的 指针
#include<stdio.h>
int main(){
int arr[] = {10, 12, 15};
int *ptr[3]; // ptr是一个指针数组
// 给指针数组赋值,指针数组的每个元素都是一个指向值的指针
for(int i = 0; i < 3; i++){
ptr[i] = &arr[i]; // ptr 的每一个元素都是 数组arr每一个元素的地址,该地址(指针)指向了arr每个元素的值
}
// 通过指针数组来获取 数组元素的值
for(int i = 0; i < 3; i++){
printf("%d \n", *ptr[i]); // *ptr[i] 是 ptr[i] 指针 对应的值
}
return 0;
}
// 字符串指针数组
#include<stdio.h>
int main(){
char *ptr[] = {"ABC", "DEF", "GHI"};
// 通过指针数组来获取 数组元素的值
for(int i = 0; i < 3; i++){
printf("%s \n", ptr[i]);
}
return 0;
}
八、动态内存分配
不同类型的数据在内存中的分配情况:① 静态变量和全局变量:静态存储区;② 非静态的局部变量:动态存储区(栈);③ 临时使用的数据:动态存储区(堆),需要时随时开辟,不需要时及时释放。
动态内存分配:根据需要向系统申请所需大小的空间,由于未在声明部分定义其为变量或者数组,不能通过变量名或者数组名来引用这些数据,只能通过指针来引用。使用完堆上分配的内存后要及时释放,否则会导致内存泄露。
内存动态分配的相关函数(在 <stdlib.h> 中):
(1) void * malloc (usigned int size):在内存的动态存储区中分配一个长度为size的连续空间,形参size的类型为无符号整型,函数返回值是所分配区域的第一个字节的地址,即此函数是一个指针型函数,返回的指针指向该分配域的开头位置。例如:malloc(100); //开辟100字节的临时空间,返回值为其第一个字节的地址。
(2)void *calloc (unsigned n, unsigned size):在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,函数返回指向所分配域的起始位置的指针,分配不成功,返回NULL。用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。例如:p = calloc(50,4); //开辟 50*4 个字节临时空间,把起始地址分配给指针变量 p。
(3)void free (void *p):释放变量p所指向的动态空间,使这部分空间能重新被其他变量使用,p是最近一次调用calloc或malloc函数时的函数返回值,free函数无返回值。例如:free(p); // 释放p 所指向的已分配的动态空间。
(4)void *realloc (void *p,unsigned int size):重新分配malloc或calloc函数获得的动态空间大小,将p指向的动态空间大小改变为size,p的值不变,分配失败返回NULL。例如:realloc(p,50); // 将p所指向的已分配的动态空间 改为50字节。
malloc,calloc,realloc 函数的基类型定为 void 类型,这种指针称为无类型指针,即不指向哪一种具体的类型数据,只表示用来指向一个抽象的类型的数据,即仅提供一个纯地址。
#include<stdio.h>
#include<stdlib.h>
int main(){
int *arr;
// arr指向开辟的动态存储区的首地址
arr = malloc(20); // arr是int *,malloc返回值是void *,这里相当于自动进行了类型转换 arr = (int *)malloc(20)
for(int i = 0; i < 5; i++){
arr[i] = i;
}
for(int i = 0; i < 5; i++){
printf("%d ", arr[i]); // 0 1 2 3 4
}
free(arr); // 销毁堆区 arr 指向的整个空间
return 0;
}
九、总结
变量定义 | 含义 |
int i | 定义整型变量 i |
int *p | 定义 p为指向整型数据的指针变量 |
int a[5] | 定义整型数组 a,包含5个元素 |
int *p[5] | 定义指针数组 p,其中每个元素都是一个指向int类型值 的 指针 |
int (*p)[5] | 定义 p 为指向 包含5个元素的一维数组 的指针变量 |
int f() | 定义 f 为返回整型值的函数 |
int *p() | 定义 p 为返回一个指针的函数,该指针指向整型数据 |
int (*p) () | 定义 p 为函数指针,指向某函数(该函数返回一个整型值)的地址 |
int **p | 定义 p 是一个指针变量,它指向一个指向整型数据的指针变量 |
void *p | 定义 p 是一个指针变量,基类型为 void(空类型),不指向具体的对象 |