C语言指针详解

目录

一、指针的基本概念

二、如何理解指针

三、指针的声明与初始化

1. 声明

2. 初始化

四、指针的基本操作

1. 解引用

2. 修改指针所指向的变量

3. 指针的加减运算

4. 指针的比较

五、指针的大小与类型

1. 指针的大小

2. 指针的类型

六、指针与数组

1. 指针与数组的关系

2. 指针数组

3. 数组指针

七、指针与函数

1. 指针作为函数参数

2. 指针函数

3. 函数指针

4. 函数指针的应用

八、指针的常见问题及注意事项

1. 野指针

2. 指针类型不匹配

3. 内存泄漏

4. 指针的指针

九、总结


一、指针的基本概念

指针是一种特殊的变量,它存储的是另一个变量的内存地址。通过指针,可以间接访问和操作变量。指针的引入使得程序能够更加灵活地操作内存,实现动态内存分配、数据结构的构建等功能。

二、如何理解指针

简单的想一下,在一个美好的假期你出去外地旅游,你去了一家偌大的宾馆办理入住,你第一次去,找不到对应的房间,而那些房间从外面看起来一摸一样。而这个时候,你该怎么办呢?宾馆的房间号在这时候是不是就起作用了?

而内存呢,也是类似于这样的,将内存空间切割为一个个小的内存单元,并对其进行编号。其编号就是地址,也称为指针。

1、指针就是地址,是内存中一个最小单元的编号

2、口语中的指针其实指的是指针变量,是用来存放内存地址的变量

3、指针变量里面存放的是地址,而通过这个地址,就可以找到一个内存单元

4、指针的大小:在32位平台是4个字节,64位平台是8个字节
    (x86-->32位环境,x64-->64位环境)

理解过程:内存空间是如何管理的?

(1)切割成内存单元,每个内存单元为1个byte(字节)

(2)给每个内存单元进行编号(编号的意义:能够快速找到指定的内存单元),把内存单元的编号称为地址

(3)指针就是地址,地址就是编号,即:指针就是内存单元的编号

三、指针的声明与初始化

1. 声明

指针的声明格式为:

类型名 *指针变量名;

示例:

int *p; // 声明一个指向整型变量的指针
float *f; // 声明一个指向浮点型变量的指针

2. 初始化

指针在声明后需要进行初始化,即将一个有效的内存地址赋给指针变量。常见的初始化方法有:

将变量的地址赋给指针

int a = 10;
int *p = &a; // 将变量a的地址赋给指针p

将动态分配的内存地址赋给指针

int *p = (int *)malloc(sizeof(int)); // 动态分配一个整型变量的内存空间,并将地址赋给指针p

将其他指针的值赋给指针

int *p1 = &a;
int *p2 = p1; // 将指针p1的值赋给指针p2

四、指针的基本操作

1. 解引用

通过解引用运算符“*”可以访问指针所指向的变量。

示例:

int a = 10;
int *p = &a;
printf("%d\n", *p); // 输出指针p所指向的变量a的值,即10

2. 修改指针所指向的变量

示例:

*p = 20; // 将指针p所指向的变量a的值修改为20

3. 指针的加减运算

对于指向数组的指针,可以通过加减运算实现指针的移动,从而访问数组中的不同元素。

示例:

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 将数组arr的首地址赋给指针p
printf("%d\n", *(p + 1)); // 输出数组arr的第二个元素,即2

4. 指针的比较

可以比较两个指针的值(即它们所存储的地址),判断它们是否指向同一个变量或内存位置。
示例:

int *p1 = &a;
int *p2 = &a;
if (p1 == p2) {
    printf("p1和p2指向同一个变量\n");
}

允许指向数组元素的指针与指向数组最后一个元素的后一个位置的指针进行比较,但是不允许与指向第一个元素之前的那个位置的指针进行比较。

五、指针的大小与类型

1. 指针的大小

在大多数现代架构中,指针的大小通常是固定的,

32位系统:指针大小为4字节。

64位系统:指针大小为8字节。

指针的大小取决于系统的地址空间大小,而不是它所指向的变量类型。

示例:

int *p1;
float *p2;
char *p3;

printf("Size of int pointer: %zu\n", sizeof(p1)); // 输出4或8
printf("Size of float pointer: %zu\n", sizeof(p2)); // 输出4或8
printf("Size of char pointer: %zu\n", sizeof(p3)); // 输出4或8

在32位系统中,所有指针的大小都是4字节;在64位系统中,所有指针的大小都是8字节。

2. 指针的类型

尽管指针的大小是固定的,但指针的类型仍然非常重要,原因如下:

类型安全:指针的类型决定了它可以指向哪种类型的变量。例如,int *只能指向int类型的变量,而float *只能指向float类型的变量。类型安全可以防止错误的操作,例如将一个float类型的值赋给一个int类型的变量。

解引用操作:指针的类型决定了解引用操作时如何解释内存中的数据。例如,int *解引用时会将内存中的数据解释为int类型,而char *解引用时会将内存中的数据解释为char类型。如果类型不匹配,可能会导致错误的结果或未定义行为。

指针运算:指针的类型影响指针的加减运算。例如,int *p加1时,p会增加sizeof(int)字节;而char *p加1时,p只会增加sizeof(char)字节(通常是1字节)。这是因为指针加1时,会增加一个与其指向的类型大小相等的偏移量。

示例:

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // p指向int类型的数组
char *c = (char *)arr; // c指向char类型的数组

printf("Value at p + 1: %d\n", *(p + 1)); // 输出2
printf("Value at c + 1: %d\n", *(c + 1)); // 输出0(因为int类型的2在内存中以多个字节表示)

在上述代码中,p + 1会跳过一个int类型的元素,而c + 1只会跳过一个char类型的元素。

六、指针与数组

1. 指针与数组的关系

数组名本质上是一个指向数组首元素的指针常量,它存储的是数组首元素的地址。

示例:

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 将数组arr的首地址赋给指针p

通过指针可以方便地访问数组中的元素:

printf("%d\n", *(arr + 2)); // 输出数组arr的第三个元素,即3

2. 指针数组

指针数组是一个数组,其元素都是指针。声明格式为:

类型名 *指针变量名[数组大小];

示例:

int *ptrArray[5]; // 声明一个包含5个指针的数组

每个指针可以指向不同的变量:

int a = 10, b = 20, c = 30;
ptrArray[0] = &a;
ptrArray[1] = &b;
ptrArray[2] = &c;
printf("%d\n", *ptrArray[0]); // 输出10
printf("%d\n", *ptrArray[1]); // 输出20
printf("%d\n", *ptrArray[2]); // 输出30

3. 数组指针

数组指针是指向整个数组的指针。声明格式为:

类型名 (*指针变量名)[数组大小];

示例:

int (*p)[5]; // 声明一个指向包含5个整型元素的数组的指针

初始化:

int arr[5] = {1, 2, 3, 4, 5};
int (*p)[5] = &arr; // 将数组arr的地址赋给数组指针p

使用:

printf("%d\n", (*p)[2]); // 输出数组arr的第三个元素,即3

七、指针与函数

1. 指针作为函数参数

通过指针传递变量的地址,函数可以直接修改调用者的变量值。

示例:

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
int main() {
    int x = 10, y = 20;
    swap(&x, &y);
    printf("x = %d, y = %d\n", x, y); // 输出x = 20, y = 10
    return 0;
}

2. 指针函数

返回值为指针类型的函数,指针指向动态分配的内存或数组等。声明格式为:

返回类型 *指针变量名(参数类型列表);

示例:

int *createArray(int size) {
    int *arr = (int *)malloc(size * sizeof(int));
    return arr;
}
int main() {
    int *arr = createArray(5);
    free(arr); // 释放动态分配的内存
    return 0;
}

3. 函数指针

函数指针是指向函数的指针,它存储的是函数的入口地址。声明格式为:

返回类型 (*指针变量名)(参数类型列表);

例如:

int (*func)(int, int); // 声明一个指向返回整型值且有两个整型参数的函数的指针

初始化:

int add(int a, int b) {
    return a + b;
}
int (*func)(int, int) = add; // 将函数add的地址赋给函数指针func

使用:

printf("%d\n", func(3, 4)); // 调用函数add,输出7

4. 函数指针的应用

回调函数:将一个函数作为参数传递给另一个函数。

函数表:定义一个函数指针数组,用于存储多个函数的地址,然后通过索引调用不同的函数。

示例:

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }

int main() {
    int (*func_table[4])(int, int) = {add, sub, mul, div}; // 定义一个函数指针数组
    printf("%d\n", func_table[0](3, 4)); // 调用add函数,输出7
    printf("%d\n", func_table[1](3, 4)); // 调用sub函数,输出-1
    printf("%d\n", func_table[2](3, 4)); // 调用mul函数,输出12
    printf("%d\n", func_table[3](3, 4)); // 调用div函数,输出0
    return 0;
}

八、指针的常见问题及注意事项

1. 野指针

指针在未初始化或已经被释放的情况下被使用,可能会导致程序崩溃或不可预测的行为。为了避免野指针,需要确保指针在使用前已经正确初始化,并在释放动态分配的内存后将指针置为NULL

2. 指针类型不匹配

指针的类型必须与它所指向的变量类型一致,否则可能会导致类型不匹配的错误。如果需要将不同类型的指针进行转换,需要使用强制类型转换。

3. 内存泄漏

在使用动态分配的内存时,如果没有及时释放分配的内存,会导致内存泄漏。为了避免内存泄漏,需要在不再使用动态分配的内存时调用free函数释放内存。

4. 指针的指针

指针也可以指向另一个指针,称为指针的指针(即二级指针)。

示例:

int a = 10;
int *p = &a;
int **pp = &p; // 定义一个指向指针p的指针pp
printf("%d\n", **pp); // 输出变量a的值

九、总结

指针是C语言中一个非常重要的概念,它为程序提供了灵活的内存操作方式。通过指针,可以实现动态内存分配、数据结构的构建、函数参数传递等功能。尽管指针的大小是固定的,但指针的类型仍然非常重要,因为它决定了指针的解引用行为、指针运算以及类型安全性。掌握指针的声明、初始化、使用以及与数组、函数的关系,对于深入理解和应用C语言具有重要意义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值