在C语言中,指针具有非常重要的地位,是其核心特性之一。C语言是一种底层的编程语言,指针的使用使得程序员可以更直接地操作计算机的内存和数据。可以说指针是C语言的灵魂。
目录
一.指针基础
1.指针的定义
指针和其他变量一样在使用之前需要定义,一般形式为:
类型说明符 *变量 例如:
int *p1;
如上示例中pi就代表一个指针变量,它的值为某个int类型数据的地址,也就是说p1指向一个int变量。具体指向还需向p1进行赋的地址决定。(这里需要注意*在示例的代码中为定义指针的意思,使用指针赋予地址时不需要加*号)
(*号一共三种用法,1定义指针使用,2双目运算符乘号,3取值符)
2.指针的赋值
指针在定义后还需要赋值后才能使用,指针变量的值只能为地址,不能为其他。
在c语言中&来表示变量的地址
如:int a;//定义一个整型数a,&a就表示a的地址
int i,*p;
p=&i;
我们也可以在定义指针的时候进行初始化
int i ,*p=&i;
3.指针变量的引用
上面提到*可以作为取值符来使用,因此我们指针的引用通常为:*p
若果我们定义一个整型数i,整形指针p,并且把指针p指向i的地址
int i =0,*p=&i;
这时我们要引用i的值我们可以用*p.
printf("%d\n",i);
printf("%d",*p);//两者等价输出都为i的值
我们可以通过指直接用变量i的名字来改变变量i的值,同样我们也可以使用指针来改变变量的值;
#include <stdio.h>
int main() {
int x = 5; // 定义一个整型变量x,初始值为5
int *p = &x; // 定义一个整型指针p,将x的地址存储在p中printf("原始值:x = %d\n", x); // 输出原始值
*p = 10; // 通过解引用操作符修改p所指向的变量的值
printf("修改后的值:x = %d\n", x); // 输出修改后的值
return 0;
}
以上会分别输x=5,x=10;
4.指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
5.指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
(1)int*ptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char
(3)int**ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
6.指针本身所占据的内存区
指针本身占了多大的内存?只要用函数sizeof(指针的类型)测一下就知道了。在32OS里,指针本身占据了4 个字节的长度,64OS占据了8个字节。
二.指针运算
指针运算是以指针变量所存放的值(地址)作为运算量而进行的运算。因此指针运算的实质就是地址的计算。
1.指针的算术运算
运算符 | 计算形式 | 意义 |
+ | p+n | 指针向地址大的方向移动n个数据 |
- | P-n | 指针向地址小的方向移动n个数据 |
++ | p++或++p | 指针向地址大的方向移动1个数据 |
-- | P--或--p | 指针向地址小的方向移动1个数据 |
- | p-q | 两指针之间相隔元素个数 |
不同数据类型之间两个指针进行加减是无意义的。
+n(-n)加(减)的是实际内存单元的地址:p+(-)sizeof(p的类型)*n;
p-q 结果是两指针指向的地址位置之间相隔的数据个数:
((p)-(q))/类型字节长度
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p = &arr[3];
int *q = &arr[1];int k = p - q;
printf("两指针之间的元素个数:%d\n", k);
return 0;
}
2.关系运算
假设有指针变量 px、py。
- px > py 表示 px 指向的存储地址是否大于 py 指向的地址
- px == py 表示 px 和 py 是否指向同一个存储单元
- px == 0 和 px != 0 表示 px 是否为空指针
//定义一个数组,数组中相邻元素地址间隔一个单元
int num[2] = {1, 3};
//将数组中第一个元素地址和第二个元素的地址赋值给 px、py
int *px = &num[0], *py = &num[1];
int *pz = &num[0];
int *pn;
//则 py > px
if(py > px){
printf("py 指向的存储地址大于 px 所指向的存储地址");
}
//pz 和 px 都指向 num[0]
if(pz == px){
printf("px 和 pz 指向同一个地址");
}
//pn 没有初始化
if(pn == NULL || pn == 0){
printf("pn 是一个空指针");
}
三.指针与数组
1.指针和一维数组
数组的数组名其实可以看作一个指针。数组的地址是数组内第一个元素的地址,数组名就代表了数组的起始地址。
int a [100] a和&a[0]是一样的
数组指针是指向数组起始地址的指针,本质为指针,一维数组的数组名为一级指针。
然而 数组名[元素下标] 可以表示数组元素
因此推导出
a[i] <=>p[i] <=> *(a+i) <=> *(p+i) 他们几个互相等价
2.指针和二维数组
指针和二维数组在C语言中有着紧密的关系。在C中,可以使用指针来访问和操作二维数组的元素。
二维数组实际上是一种特殊的数据结构,它由多个一维数组组成。每个一维数组称为二维数组的行,而整个二维数组就是行的集合。可以使用指针来访问二维数组的元素,方法是使用指针进行逐行或逐列遍历。
下面是一个简单的示例,展示了如何使用指针来访问二维数组的元素:
#include <stdio.h>
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int (*ptr)[4] = arr; // 定义一个指向int[4]的指针,将其指向二维数组arr
// 使用指针访问二维数组的元素
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", *(*(ptr + i) + j));
}
printf("\n");
}
return 0;
}
在这个示例中,我们定义了一个3x4的二维整型数组`arr`,并将其赋值。然后,我们定义了一个指针`ptr`,它是一个指向具有4个整型元素的一维数组的指针。我们将`ptr`指向二维数组`arr`,这样`ptr`就可以被用于访问二维数组的元素。
在使用指针访问二维数组时,可以将指针视为一个指向行的指针。通过逐行遍历,可以使用`*(*(ptr + i) + j)`的方式来访问特定行和列的元素。
需要注意的是,指针和二维数组之间的关系可以更加复杂,取决于具体的用途和操作。上面的示例只是展示了一种简单的情况,实际上可以使用不同的指针操作来实现不同的访问方式。
存储地址的指针变量叫做行指针变量,一般形式如下:
存储类型 数据类型 (*指针变量名)[表达式];
int a[3][2] ;
int (*p)[2];
#include <stdio.h>
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int (*ptr)[4]; // 定义行指针变量ptr
// 将行指针变量ptr指向二维数组的第二行
ptr = &arr[1];
// 使用行指针变量访问第二行的元素
for (int i = 0; i < 4; i++) {
printf("%d ", (*ptr)[i]);
}
printf("\n");
return 0;
}
在这个示例中,我们定义了一个3x4的二维整型数组arr
。然后,我们定义了一个行指针变量ptr
,它可以指向具有4个整型元素的一维数组。通过将ptr
指向二维数组arr
的第二行,我们可以使用(*ptr)[i]
的方式访问第二行的元素。
四.多级指针
多级指针是指指向指针的指针,它可以用于处理一些特定的情况,例如动态内存分配或者修改指针的值。在C语言中,可以通过在指针前面添加星号(*)来声明多级指针。
以下是一个示例,展示了如何声明和使用多级指针:
#include <stdio.h>
int main() {
int num = 10;
int *ptr; // 一级指针
int **ptr2; // 二级指针
int ***ptr3; // 三级指针
ptr = #
ptr2 = &ptr;
ptr3 = &ptr2;
printf("Value of num: %d\n", num);
printf("Value of num using 1-level pointer: %d\n", *ptr);
printf("Value of num using 2-level pointer: %d\n", *(*ptr2));
printf("Value of num using 3-level pointer: %d\n", *(*(*ptr3)));
return 0;
}
在这个示例中,我们定义了一个整型变量`num`,以及一级指针`ptr`、二级指针`ptr2`和三级指针`ptr3`。通过将它们赋值为相应变量或指针的地址,我们建立了多级指针的链接关系。
在打印输出部分,我们使用不同级别的指针来访问和输出`num`的值。通过`*ptr`,我们可以访问一级指针所指向的`num`的值;通过`*(*ptr2)`,我们可以访问二级指针所指向的一级指针所指向的`num`的值;通过`*(*(*ptr3))`,我们可以访问三级指针所指向的二级指针所指向的一级指针所指向的`num`的值。
多级指针在某些情况下非常有用,例如在函数中动态分配内存、修改指针的值或者传递指针的地址等。但是需要注意的是,多级指针的使用需要谨慎,因为指针的层级过多会增加代码的复杂性和难度。
五.指针数组
要定义一个指针数组,可以使用以下语法:
<数据类型> *<数组名>[<数组大小>];
- 定义一个包含5个整型指针的指针数组:
int *ptrArray[5];
- 定义一个包含3个字符型指针的指针数组:
char *charPtrArray[3];
- 定义一个包含4个浮点型指针的指针数组:
float *floatPtrArray[4];
在这些示例中,我们定义了不同类型的指针数组。数组的大小是在方括号中指定的,这决定了数组可以容纳的指针元素的数量。
指针数组是一个数组,其中的每个元素都是一个指针。每个指针可以指向不同类型的数据或者相同类型的数据。在C语言中,可以通过在指针类型后面添加方括号([])来声明指针数组。
以下是一个示例,展示了如何声明和使用指针数组:
#include <stdio.h>
int main() {
int num1 = 10, num2 = 20, num3 = 30;
int *ptrArr[3]; // 声明指针数组
ptrArr[0] = &num1;
ptrArr[1] = &num2;
ptrArr[2] = &num3;
printf("Value of num1: %d\n", *ptrArr[0]);
printf("Value of num2: %d\n", *ptrArr[1]);
printf("Value of num3: %d\n", *ptrArr[2]);
return 0;
}
在这个示例中,我们声明了一个指针数组`ptrArr`,它包含了3个指针元素。这些指针元素可以指向整型变量。通过将指针元素分别赋值为相应变量的地址,我们将它们与相应的变量关联起来。
在打印输出部分,我们使用指针数组来访问和输出相应变量的值。通过`*ptrArr[0]`,我们可以访问指针数组的第一个元素所指向的变量的值;通过`*ptrArr[1]`,我们可以访问指针数组的第二个元素所指向的变量的值;通过`*ptrArr[2]`,我们可以访问指针数组的第三个元素所指向的变量的值。
指针数组在一些场景中非常有用,例如在需要管理多个指针的情况下。它可以用于处理不同类型的数据,或者用于创建动态的数据结构,比如链表。需要注意的是,指针数组的大小必须在声明时确定,并且需要确保指针元素指向有效的内存地址。
六.const与指针
在C语言中,const关键字用于声明常量。当const与指针结合使用时,可以有以下几种情况:
1. const修饰指针:
const int *ptr;
这种情况下,ptr是一个指向常量整型数据的指针。这意味着我们不能通过ptr来修改所指向的数据,但是可以通过ptr来读取数据。例如:
int num = 10;
const int *ptr = #
printf("%d\n", *ptr); // 可以读取所指向的数据
*ptr = 20; // 错误,不能修改所指向的数据
2. const修饰指针本身:
int *const ptr;
这种情况下,ptr是一个指向整型数据的常量指针。这意味着ptr的值(即所指向的地址)是固定的,不能被修改,但是可以通过ptr来修改所指向的数据。例如:
int num = 10;
int *const ptr = #
printf("%d\n", *ptr); // 可以读取所指向的数据
*ptr = 20; // 可以修改所指向的数据
ptr = &anotherNum; // 错误,不能修改指针的值
3. const同时修饰指针和所指向的数据:
const int *const ptr;
这种情况下,ptr是一个指向常量整型数据的常量指针。既不能通过ptr来修改所指向的数据,也不能修改指针本身的值。例如:
int num = 10;
const int *const ptr = #
printf("%d\n", *ptr); // 可以读取所指向的数据
*ptr = 20; // 错误,不能修改所指向的数据
ptr = &anotherNum; // 错误,不能修改指针的值
综上所述,const与指针的结合可以用于声明指向常量数据的指针、常量指针以及同时为常量的指针和所指向的数据。这种使用可以提高程序的安全性和可靠性,防止意外的数据修改。
七.void指针
在C语言中,可以使用`void *`来声明一个void指针。例如:
void *ptr;
这样的声明创建了一个指针`ptr`,它可以指向任意类型的数据。这意味着`ptr`可以存储任何类型的指针。然而,由于`void *`指针没有指定类型,因此不能通过`ptr`直接访问所指向的数据。
要使用`void *`指针,通常需要将其转换为特定类型的指针。这样做可以解除类型的歧义,并允许对数据进行适当的操作。例如:
int num = 10;
void *ptr = # // 将int类型的指针转换为void类型的指针
// 将void指针转换回int指针,并通过解引用操作访问所指向的数据
int *intPtr = (int *)ptr;
printf("%d\n", *intPtr);
在这个示例中,我们首先将一个int类型的指针`&num`转换为void类型的指针,并将其赋值给`ptr`。然后,我们将`ptr`再次转换为int类型的指针`intPtr`,通过解引用操作访问所指向的int数据,并打印出该值。使用`void *`指针需要小心,确保在访问所指向的数据之前将其正确转换为适当的类型。否则,可能会导致类型错误和未定义行为。
void
指针是一种特殊类型的指针,它可以指向任意类型的数据,包括函数指针。void
指针在C语言中的声明方式为void *
。
由于
void
指针可以指向任意类型的数据,因此它在以下几种情况下特别有用:
作为参数传递:当函数的参数类型未知或可能是多种类型时,可以使用
void *
作为参数类型。在函数内部可以根据实际情况进行类型转换,以访问和处理数据。动态内存分配:在使用动态内存分配函数(如
malloc
、calloc
等)分配内存时,返回的是void *
类型的指针。需要将其转换为所需类型的指针,以便正确使用分配的内存空间。泛型编程:
void
指针在一些需要处理不同类型数据的通用算法或数据结构中非常有用。通过使用void *
,可以实现对不同类型数据的操作和处理。