c语言指针的用法
c语言是一种高级编程语言,它可以直接操作内存中的数据。指针是c语言中一种特殊的变量,它可以存储另一个变量的地址,也就是内存中的位置。通过指针,我们可以间接地访问或修改内存中的数据,从而实现更高效和灵活的编程。本文将介绍c语言指针的基本概念、定义和初始化、运算和应用,以及一些常见的错误和注意事项。希望本文能够帮助你掌握c语言指针的用法,提高你的编程水平。
指针的基本概念
指针是一种数据类型,它可以存储一个地址值,也就是内存中某个位置的编号。每个变量在内存中都有一个唯一的地址,我们可以用指针来记录这个地址,然后通过这个地址来访问或修改变量的值。
例如,假设有一个整型变量a,它的值为10,它在内存中的地址为1000(为了简化,我们假设地址是十进制数)。我们可以定义一个指向整型的指针p,并把a的地址赋给p,如下所示:
int a =10; // 定义一个整型变量a,赋值为10
int*p; // 定义一个指向整型的指针p
p =&a; // 把a的地址赋给p
这里,&a表示取a的地址,也就是1000。p = &a表示把1000赋给p,也就是让p指向a。
从图中可以看出,p和a是两个不同的变量,它们占用不同的内存空间。p存储了a的地址,也就是1000。我们可以通过p 来间接地访问或修改a的值。
指针的定义和初始化
指针是一种数据类型,它需要在使用前进行定义和初始化。定义指针时,需要指定它所指向的变量的类型。初始化指针时,需要给它赋一个有效的地址值。
定义指针的一般格式为:
type *pointer_name;
其中,type表示指针所指向的变量的类型,如int、char、float等;pointer_name表示指针的名称,如p、q、ptr等;*表示这是一个指针类型。
例如:
int*p; // 定义一个指向整型的指针p
char*q; // 定义一个指向字符型的指针q
float*ptr; // 定义一个指向浮点型的指针ptr
注意,在定义多个指针时,每个指针前都要加*号,不能省略。例如:
int*p, *q; // 正确scanf输入整型数组
int* p, q; // 错误,q不是指针
初始化指针时,需要给它赋一个有效的地址值。一般有两种方法:
使用取地址符&获取一个已存在变量的地址,并赋给指针。
使用malloc函数动态分配一块内存,并返回其首地址,并赋给指针。
例如:
int a =10; // 定义一个整型变量a,并赋值为10
int*p =&a; // 定义一个指向整型的指针p,并把a的地址赋给它
char*q = (char*)malloc(sizeof(char)); // 定义一个指向字符型的指针q,并动态分配一块字符型大小的内存,并把其首地址赋给它
注意,使用malloc函数时,需要指定分配内存的大小,一般用sizeof函数来获取某种类型的大小。另外,malloc函数返回的是void 类型的指针,需要根据实际情况进行强制类型转换,例如(char )表示转换为指向字符型的指针。
指针的运算
指针是一种特殊的变量,它可以进行一些特殊的运算。常见的指针运算有以下几种:
取值运算:使用*号可以获取指针所指向的变量的值,也就是指针所存储的地址处的数据。这个运算符也叫做解引用或间接访问运算符。
赋值运算:可以给指针赋一个值,让它指向另一个变量或内存空间。也可以通过*号给指针所指向的变量赋一个新的值,从而修改内存中的数据。
指针与整数的加减运算:可以给指针加上或减去一个整数,让它指向相邻的内存单元。这个运算符也叫做指针的移动或偏移。
指针之间的减法运算:可以计算两个同类型的指针之间的距离,也就是它们所指向的内存单元之间相差多少个单位。这个运算符也叫做指针的比较或差值。
下面我们用一些例子来说明这些运算。
取值运算
假设有以下代码:
int a =10; // 定义一个整型变量a,并赋值为10
int*p =&a; // 定义一个指向整型的指针p,并把a的地址赋给它
那么,我们可以用*p来获取p所指向的变量a的值,如下所示:
printf("%d\n", *p); // 输出10
这里,*p表示取p所存储的地址处的数据,也就是a的值。
注意,p和p是不同的概念,p表示p所指向的变量的值,而p表示p本身存储的地址值。例如:
printf("%d\n", p); // 输出1000
printf("%d\n", *p); // 输出10
赋值运算
假设有以下代码:
int a =10; // 定义一个整型变量a,并赋值为10
int b =20; // 定义一个整型变量b,并赋值为20
int*p =&a; // 定义一个指向整型的指针p,并把a的地址赋给它
那么,我们可以用以下两种方式来修改变量a和b的值:
直接修改a和b本身:
a =30; // 把30赋给a
b =40; // 把40赋给b
通过*p间接修改a和b:
*p =30; // 把30赋给*p,也就是a
p =&b; // 把b的地址赋给p,让p指向b
*p =40; // 把40赋给*p,也就是b
这里,p = 30表示把30赋给p所存储的地址处的数据,也就是a。p = &b表示把b的地址赋给p,让p指向b。p = 40表示把40赋给p所存储的地址处的数据,也就是b。
注意,通过指针修改变量的值时,要确保指针已经指向了一个有效的地址,否则会出现错误。例如:
int*p; // 定义一个指向整型的指针p,但没有初始化
*p =10; // 错误,p没有指向任何有效的地址,不能通过*p赋值
指针与整数的加减运算
假设有以下代码:
int a[5] = {1, 2, 3, 4, 5}; // 定义一个整型数组a,并初始化
int*p = a; // 定义一个指向整型的指针p,并把a的首地址赋给它
那么,我们可以用以下方式来访问或修改数组a中的元素:
使用下标运算符[]:
printf("%d\n", a[0]); // 输出1
printf("%d\n", a[1]); // 输出2
a[2] =6; // 把6赋给a[2]
a[3] =7; // 把7赋给a[3]
使用指针加减整数的运算:
printf("%d\n", *p); // 输出1,等价于a[0]
printf("%d\n", *(p +1)); // 输出2,等价于a[1]
*(p +2) =6; // 把6赋给*(p + 2),等价于a[2]
*(p +3) =7; // 把7赋给*(p + 3),等价于a[3]
这里,p表示取p所存储的地址处的数据,也就是a[0]。(p + 1)表示取p加上1后所存储的地址处的数据,也就是a[1]。类似地,(p + 2)表示a[2],(p + 3)表示a[3]。
数组在内存中是连续存储的,每个元素占用相同大小的空间。指针加上或减去一个整数时,并不是简单地加上或减去这个数值,而是根据指针所指向的变量的类型来计算偏移量。例如,如果指针是指向整型的,那么每加上或减去一个整数,就相当于偏移一个整型大小的空间。如果指针是指向字符型的,那么每加上或减去一个整数,就相当于偏移一个字符型大小的空间。
注意,在使用指针加减整数的运算时,要确保指针不越界,也就是不要访问或修改超出数组范围的内存单元。例如:
int a[5] = {1, 2, 3, 4, 5}; // 定义一个整型数组a,并初始化
int*p = a; // 定义一个指向整型的指针p,并把a的首地址赋给它
printf("%d\n", *(p +5)); // 错误,越界访问,*(p + 5)相当于a[5],但是数组下标只能从0到4
*(p +6) =8; // 错误,越界修改,*(p + 6)相当于a[6],但是数组下标只能从0到4
指针之间的减法运算
假设有以下代码:
int a[5] = {1, 2, 3, 4, 5}; // 定义一个整型数组a,并初始化
int*p = a; // 定义一个指向整型的指针p,并把a的首地址赋给它
int*q = a +3; // 定义一个指向整型的指针q,并把a加上3后的地址赋给它,也就是a[3]的地址
那么,我们可以用以下方式来计算两个指针之间的距离:
printf("%d\n", q - p); // 输出3,表示q和p之间相差3个整型大小的空间,也就是q指向的元素比p指向的元素大3个下标
指针之间的减法运算,并不是简单地减去两个地址值,而是根据指针所指向的变量的类型来计算差值。例如,如果指针是指向整型的,那么两个指针之间的差值就相当于它们所指向的元素之间相差多少个下标。如果指针是指向字符型的,那么两个指针之间的差值就相当于它们所指向的字符之间相差多少个位置。
注意,在使用指针之间的减法运算时,要确保两个指针是同类型的,也就是指向同一种类型的变量或数组。否则,这个运算没有意义,也可能出现错误。例如:
int a[5] = {1, 2, 3, 4, 5}; // 定义一个整型数组a,并初始化
char b[5] = {'a', 'b', 'c', 'd', 'e'}; // 定义一个字符型数组b,并初始化
int*p = a; // 定义一个指向整型的指针p,并把a的首地址赋给它
char*q = b; // 定义一个指向字符型的指针q,并把b的首地址赋给它
printf("%d\n", q - p); // 错误,不同类型的指针不能相减
指针的应用
指针是c语言中一种非常强大和灵活的工具,它可以用于实现很多高级和复杂的功能。常见的指针的应用有以下几种:实现动态内存分配:使用malloc、free等函数可以在程序运行过程中动态地分配和释放内存空间,从而实现更灵活和高效的内存管理。这些函数都需要使用指针来操作内存。
实现函数参数的传递:使用指针作为函数参数可以实现传址调用,也就是让函数直接操作实参所在的内存空间,从而实现实参和形参之间的数据交换。这样可以提高函数的效率和灵活性。
实现数据结构:使用指针可以构造一些复杂的数据结构,如链表、树、图等。这些数据结构可以存储和处理更多样化和动态化的数据,从而实现更强大和高级的功能。
实现字符串操作:使用指针可以方便地操作字符串,如获取字符串长度、比较字符串大小、拷贝字符串内容、连接字符串等。这些操作都需要使用到字符串首地址或末尾地址等信息。
实现多维数组:使用指针可以实现多维数组,如二维数组、三维数组等。这些数组可以存储和处理更复杂和丰富的数据,从而实现更多样化和高级化的功能。
下面我们用一些例子来说明这些应用。
实现动态内存分配
假设我们要实现一个动态数组,也就是可以根据用户的输入来确定数组的大小,并且可以在运行过程中改变数组的大小。我们可以使用指针和malloc、free等函数来实现这个功能,如下所示:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int n; // 定义一个整型变量n,用来存储用户输入的数组大小
int*a; // 定义一个指向整型的指针a,用来指向动态分配的数组空间
int i; // 定义一个整型变量i,用来作为循环变量
printf("请输入数组的大小:\n");
scanf("%d", &n); // 从键盘读取用户输入的数组大小,并赋给n
a = (int*)malloc(n *sizeof(int)); // 根据n的值,动态分配n个整型大小的内存空间,并把其首地址赋给a
if (a ==NULL) // 判断是否分配成功,如果失败,打印错误信息并退出程序
{
printf("内存分配失败!\n");
exit(1);
}
printf("请输入数组的元素:\n");
for (i =0; i < n; i++) // 从键盘读取用户输入的数组元素,并赋给a所指向的内存空间
{
scanf("%d", a + i); // 等价于scanf("%d", &a[i]);
}
printf("您输入的数组是:\n");
for (i =0; i < n; i++) // 打印a所指向的内存空间中的数据,也就是数组元素
{
printf("%d ", *(a + i)); // 等价于printf("%d ", a[i]);
}
printf("\n");
free(a); // 释放a所指向的内存空间,避免内存泄漏
return0;
}
运行结果:
请输入数组的大小:
5
请输入数组的元素:
1 2 3 4 5
您输入的数组是:
1 2 3 4 5
从上面的例子可以看出,使用指针和malloc、free等函数可以实现动态内存分配,从而实现更灵活和高效的内存管理。这样可以避免浪费或不足的情况,也可以根据需要改变内存空间的大小。
注意,在使用动态内存分配时,要注意以下几点:
使用malloc函数时,要指定分配内存的大小,一般用sizeof函数来获取某种类型的大小。另外,malloc函数返回的是void *类型的指针,需要根据实际情况进行强制类型转换。
使用free函数时,要确保释放的是由malloc函数分配的内存空间,并且只释放一次。否则,可能会出现内存泄漏或重复释放的错误。
在使用动态分配的内存空间时,要确保不越界访问或修改。否则,可能会出现数据丢失或覆盖的错误。
在使用完动态分配的内存空间后,要及时释放它们。否则,可能会出现内存泄漏或不足的错误。
实现函数参数的传递
假设我们要实现一个交换两个整型变量值的函数。我们可以使用指针作为函数参数来实现这个功能,如下所示:
#include<stdio.h>
void swap(int*p, int*q) // 定义一个交换两个整型变量值的函数,使用指针作为参数
{
int temp; // 定义一个临时变量,用来存储交换过程中的中间值
temp =*p; // 把*p的值赋给temp,也就是把a的值赋给temp
*p =*q; // 把*q的值赋给*p,也就是把b的值赋给a
*q = temp; // 把temp的值赋给*q,也就是把a的值赋给b
}
int main()
{
int a =10; // 定义一个整型变量a,并赋值为10
int b =20; // 定义一个整型变量b,并赋值为20
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论