提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
指针在C语言中是很重要的内容,也是比较难理解的内容,需要我们反复巩固才能可以对其有所理解,在刷题过程中,遇到了各式各样的指针的问题,这里进行总结。指针的基础就不普及了,直接上坑
一、如何分析指针
看着一个指针,需要搞清楚:指针的类型、指针所指向的类型、指针的值(指针所指向的内存区)、指针本身所占据的内存区
1)指针的类型:
int *ptr; //指针的类型是 int *
char *ptr; //指针的类型是 char *
只要把指针声明语句里的指针名字去掉, 剩下的部分就是这个指针的类型。这是指针本身所具有的类型
2)指针所指向的类型
int *ptr; //指针的类型是 int
char ptr; //指针的类型是 char
只须把指针声明语句中的指针名字和名字左边的指针声明符去掉, 剩下的就是指针所指向的类型
3) 指针的值
指针的值是指针本身存储的数值, 这个值将被编译器当作一个地址, 而不是一个一般的数值。在 32 位程序里, 所有类型的指针的值都是一个 32 位 整数, 因为 32 位程序里内存地址全都是 32 位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始, 长度为 sizeof(指针所指向的类型)的一片内存区。
以后, 我们说一个指针的值是 XX, 就相当于说该指针指向了以 XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
指针所指向的内存区和指针所指向的类型是两个完全不同的概念。以后, 每遇到一个指针, 都应该问问:这个指针的类型是什么?指针指向的类型是什么?该指针指向了哪里?
4)指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数 sizeof(指针的类型)测一下就知道了。
在 32 位平台里, 指针本身占据了 4 个字节的长度。在 64位平台里, 指针本身占据了 8 个字节的长度。
二、数组和指针的关系(3种)
数组指针
指针数组
二维数组指针
1.数组指针和指针数组
指针数组和函数的区别:
1、int(*p)[4];------定义数组指针,为指向含4个元素的一维整形数组的指针变量(本质是指针)
2、int *p[4];-------定义指针数组p,它由4个指向整型数据的指针元素组成(本质是数组)
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针,在32 位系统下任何类型的指针永远是占4 个字节。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下任何类型的指针永远是占4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。它是“指向数组的指针”的简称。
<1>数组指针:指向数组的指针
代码如下(示例):
#include <stdio.h>
int main(void)
{
int arr[] = {0, 1, 2, 3, 4};
int *p = &arr[3]; //也可以写作 int *p = arr + 3;
printf("%d, %d, %d, %d, %d\n",
*(p-3), *(p-2), *(p-1), *(p), *(p+1) );
return0;
}
运行结果为:
0, 1, 2, 3, 4
<2>指针数组:数组中每个元素都是指针,比如:
int a=1,b=2,c=3;
int *arr[3] = {&a,&b,&c};
示例程序:
#include <stdio.h>
int main(void)
{
int a = 1, b = 2, c = 3;
//定义一个指针数组
int *arr[3] = {&a, &b, &c};//也可以不指定长度,直接写作 int *parr[]
printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]);
return0;
}
运行结果为:
1, 2, 3
2.二维数组指针
二维数组指针:指向二维数组的指针。如:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
int (*p)[4] = a;
a [3] [4]表示一个3行4列的二维数组,其所有元素在内存中是连续存储的。
例子:
详情可以看下图:
注意:二维数组a ,* ( *(a + 1)+2),当中的 *(a + 1)不是一个解引用,是针对二维数组 a 的操作,可以看作是一个一维数组
三、函数指针和指针函数
函数、指针这两个词结合的顺序不同其意义也不同,即指针函数与函数指针的意义不同。
函数指针和指针函数的区别:
函数指针:int (*f)(int x,int y);
指针函数:int *f(int x,int y);
函数指针本质是一个指针,其“指向”一个函数。
指针函数本质是一个函数,其“返回”值为指针。
1.函数指针
代码如下(示例):
下面是一个简单的例子,展示了如何使用函数指针,看代码看注释体会:
#include <stdio.h>
// 定义一个函数类型
typedef int (*Operation)(int, int);
// 加法函数
int add(int a, int b) {
return a + b;
}
// 减法函数
int subtract(int a, int b) {
return a - b;
}
// 乘法函数
int multiply(int a, int b) {
return a * b;
}
// 使用函数指针进行计算操作,并返回结果
int calculate(Operation operation, int a, int b) {
return operation(a, b);
}
int main() {
// 声明几个变量用于测试计算操作
Operation op; // 声明一个函数指针变量
op = add; // 将add()赋值给op,使其指向add()函数
printf("Addition: %d\n", calculate(op, 5, 3)); // 调用calculate()并传入op作为参数,输出8
op = subtract; // 将subtract()赋值给op,使其指向subtract()函数
printf("Subtraction: %d\n", calculate(op, 5, 3)); // 调用calculate()并传入op作为参数,输出2
op = multiply; // 将multiply()赋值给op,使其指向multiply()函数
printf("Multiplication: %d\n", calculate(op ,5 ,3));//调用calculate()并传入op作为参数 输出15
return 0;
}
在上述代码中:
-
首先,我们定义了一个函数指针类型
Operation
,它可以指向返回值为整数、接受两个整数参数的函数。 -
然后,我们实现了三个不同的计算操作:加法、减法和乘法。每个操作都是一个具有相应功能的函数。
-
接下来,我们定义了一个名为
calculate
的函数。该函数接受一个Operation
类型的参数,并使用该参数调用传入的操作进行计算并返回结果。 -
在主函数中,我们声明了一个名为
op
的变量作为函数指针。然后将其分别赋值给加法、减法和乘法操作。最后通过调用calculate()
函数来执行这些操作,并输出结果。
总结起来,使用函数指针可以在运行时动态地选择要执行哪个特定的代码块或者功能。这种灵活性使得程序能够根据需要选择不同的行为或逻辑路径。
2.指针函数
代码如下(示例):下面是一个简单的指针函数示例,它接受两个整数作为参数,并返回它们的和,看代码看注释体会
#include <stdio.h>
int* add(int a, int b) {
int sum = a + b;
return ∑
}
int main() {
int num1 = 5;
int num2 = 10;
// 调用add函数并将返回值赋给result指针
int* result = add(num1, num2);
printf("Sum: %d\n", *result); // 输出结果
return 0;
}
在这个例子中,add
函数接受两个整数 a
和 b
。它首先计算它们的和,并将结果存储在局部变量 sum
中。然后,通过使用取地址运算符 &
返回了 sum
的地址。
在主函数中,我们声明了两个整型变量 num1
和 num2
并分别初始化为 5 和 10。然后我们调用了 add()
函数,并将其返回值赋给名为 result
的指针变量。
最后,在打印语句中,我们使用解引用运算符(即星号)来访问指针所指向的内存位置上的值,并输出结果。
需要注意的是,在这个例子中,由于局部变量 sum 是在函数栈帧上创建的,在函数执行完毕后会被销毁。因此,在主函数中访问该地址可能导致未定义的行为。这只是一个简单示例,用于说明指针函数的基本概念和使用方法。在实际编程中,需要谨慎处理指针的生命周期和作用域问题。
四、指针与数组
数组与指针在多数下面是可以等价的,例如:
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value=array[0]; //也可写成:value=*array;
value=array[3]; //也可写成:value=*(array+3);
value=array[4]; //也可写成:value=*(array+4)
但是也有不等价的时候,比如下面三种情况:
1、数组名不可以改变,而指向数组的指针是可以改变的。
2、字符串指针指向的字符串中的字符是不能改变的,而字符数组中的字符是可以改变的。
3、求数组长度时,借用数组名可求得数组长度,而借用指针却得不到数组长度。
下面就是代码来诠释上面的三种情况
1)数组名不可以改变,而指向数组的指针是可以改变的,在C语言中,数组名是一个常量指针,它指向数组的第一个元素。因此,不能直接修改数组名来指向其他位置的内存。
代码如下(示例):
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // 数组名arr被隐式转换为指向第一个元素的指针
// 尝试修改数组名
arr = &arr[1]; // 错误!无法将数组名赋值给另外一个地址
// 修改指向数组的指针
ptr = &arr[1]; // 正确!可以将ptr重新赋值为其他地址
上述代码中,我们定义了一个整型数组arr
和一个整型指针ptr
。由于数组名是不可更改的常量指针,所以我们无法直接将其赋值给另外一个地址。但是,我们可以通过使用普通变量(如ptr
)来间接地操作和改变对同一块内存区域(即该数组)的引用。
2)字符串指针指向的字符串中的字符是不能改变的,而字符数组中的字符是可以改变的。
代码如下(示例):
#include <stdio.h>
int main() {
char str[] = "Hello";
char *ptr = "World";
// 修改字符数组中的字符
str[0] = 'h';
// 尝试修改字符串指针指向的字符串中的字符,会导致编译错误
// ptr[0] = 'w';
printf("str: %s\n", str);
printf("ptr: %s\n", ptr);
return 0;
}
在上面的代码中,我们定义了一个字符数组str
和一个字符串指针ptr
。 str
是一个可变长度的字符数组,它可以通过索引来访问和修改其中的元素。我们将其初始化为"Hello"。
而对于字符串指针ptr
,它只是一个指向常量字符串"World"的指针,并且不能直接修改该常量字符串中的任何元素。因此,在尝试修改ptr[0] = 'w'
时会导致编译错误。
最后,我们打印出了两个变量以验证结果。输出应该是:
str: hello
ptr: World
这说明虽然我们成功地改变了字符数组中第一个字母为小写"h",但无法更改由字符串指针所引用的常量字符串。
3)求数组长度时,借用数组名可求得数组长度,而借用指针却得不到数组长度,在C语言中,数组名实际上是一个指向数组第一个元素的指针。因此,可以通过借用数组名来获取数组的长度。
以下是使用数组名求取数组长度的示例代码:
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int length = sizeof(arr) / sizeof(arr[0]);
printf("Array length: %d\n", length);
return 0;
}
输出结果为:
Array length: 5
在这个示例中,sizeof(arr)
返回整个数组所占用的字节数,而sizeof(arr[0])
返回单个元素所占用的字节数。通过将两者相除,我们可以得到整个数组中元素的数量。
然而,在C语言中借用指针无法直接获得数组长度。因为当你传递一个指向某个特定类型数据(如int)的指针时,并没有任何信息表明该指针所引用内存块有多大或包含多少元素。因此,在不知道具体大小和结构情况下,无法准确地计算出由该指针引用的对象(例如:动态分配内存、函数参数等)所包含元素数量。
如果需要确定动态分配内存或函数参数等情况下的数组长度,则需要额外记录或传递相关信息以便进行计算和管理。
总结
以上就是一些指针的知识点相关基础知识总结,,最后附上一些指针的题例子: