目录
前言
指针是C语言中的一个重要概念及其特点也是掌握 C语言比较困难的部分。 指针也就是内存地址 ,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的 存储空间 长度也不同。 有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。
一.指针概念
1.1 指针的类型
去掉指针本身剩下的就是指针的类型
- int*ptr;//指针的类型是int*
- char*ptr;//指针的类型是char*
- int**ptr;//指针的类型是int**
- int(*ptr)[3];//指针的类型是int(*)[3]
- int*(*ptr)[4];//指针的类型是int*(*)[4]
1.2 指针所指向的类型
去掉指针本身和指针前面的*剩下的就是指针所指向的类
- int*ptr; //指针所指向的类型是int
- char*ptr; //指针所指向的的类型是char
- int**ptr; //指针所指向的的类型是int*
- int(*ptr)[3]; //指针所指向的的类型是int()[3]
- int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
1.3 指针的值
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。64位也是如此,在64位程序中,所有类型的指针的值都是64位整数。指针所存储的值是地址。
1.4 &和*
- &:&在指针运算中为取地址符号
- * : *在指针运算中为获取地址所指向内存单元的值的符号
int* p; //定义指针
int a=1; //定义变量
p=&a; //赋值指针
printf("p:%d,a:%d",*p,a);
运行结果
p:1a:1
&*p和*&p的区别:
因为*和&具有相同的优先级,且是右结合性(从右向左),故分析可得:
1)&*p:相当于&(*p),首先进行一次*p运算再取地址。若p是一个非指针变量,*p是非法的;若p是一个指针变量,&(*p)=p;
2)*&p:相当于*(&p),先取地址再做*运算。若p是一个非指针变量,*(&p)=p;若p是一个指针变量,*(&p)=p。
1.5 指针的运算
对于指向数组的指针变量,可以加上或减去一个整数n,这意味着把指针从当前指向的位置(指向的某个数组元素)向后或向前移动n个位置。
这里应当注意:
(1)数组指针变量向前向后移动一个位置和地址加1或减1在概念上是不同的。因为数组的元素可以由不同的数据类型,所占用的字节长度也是不同的。如指针变量加1,即表示指针变量指向下一个元素的首地址,而不是在原地址的基础上加1;
(2)指针变量的加减运算只能对数组指针变量进行,对只想其他数据类型的指针变量做加减运算是毫无意义的。
(3)两个指针变量之间的运算:只有指向同一数组的两个指针变量才能进行运算,否则运算是毫无意义的。
(4)两指针变量相减:两指针变量相减所得之差,是两个指针所指向的数据元素之间相差的元素个数,乘以该数组每个数据元素的长度(字节数);
(5)两指针变量相加:两指针变量不能进行加法运算,毫无实际意义。
int*p; //p所指向的类型为int类型
p++; //p+1地址数增加四个字节
char* p1; //p1所指向的类型为char类型
p1++; //p1+1地址数增加1个字节
二.指针与一维数组
2.1 一维数组
2.1.1 一维数组的初始化
(1)在定义数组时对所有数组元素赋初值
int data[5]={100,99,98,97,95};
(2)对数组部分元素赋初值
int data[5]={100,99};
将100、99赋给data[0]、data[1],其余数组元素自动赋值0。
(3)对全部数组元素赋处置,省略数组长度
int data[]={100,99,98,97,95};
将初值依次赋值给数组的各个元素,数组长度由初值个数决定。
2.2 指针与一维数组
在c语言中,获取数组首地址的方法有两种:
第一种用&运算符获取数组中头一个元素的地址,如&a[0]。
第二种用数组名代表数组的首地址,所以语句“p=&a[0]”等价于“p=a”
对于数组元素的访问,下标法和指针法是等价的。假设有一个数组a,指针p指向数组a的首地址,数组a的元素的表示方法有以下两种:
(1)下标法:a[i]或p[i]
(2)指针法:*(a+i)或*(p+i)
三.指针与二维数组
3.1 二维数组
3.1.1 二维数组的初始化
(1)按行给二维数组初始化
int socre[4][3]={{99,98,97},{96,95,94},{93,92,91},{90,89,88}}
99 | 98 | 97 |
96 | 95 | 94 |
93 | 92 | 91 |
90 | 89 | 88 |
(2)按排列数序初始化
int score=[4][3]{99,98,97,96,95,94,93,92,91,90,89,88}
(3)对部分元素赋初值
int socre[4][3]={{99,98},{96,95,}}
int score[4][3]={99,98,97,96,95,94,93,92}
对二维数组进行赋值,为赋初值的数组元素,系统自动赋值为0
(4)初始化时省略第一维长度
int socre[][3]={{99,98},{96,95,}}
int score[][3]={99,98,97,96,95,94,93,92}
3.2 二维数组与指针
3.2.1 二维数组与指针的关系
在c语言中,二维数组可以看作是一维数组的嵌套而构成的,一个二维数组可以按行分解成多个一维数组。例如,有如下定义:
int a[3][4];
二维数组a可分解为三个一维数组,其数组名分别为a[0],a[1],a[2]。每个一维数组有四个元素。
c语言规定数组名代表数组的首地址,因此a[i]是第i行第0列的数组元素(a[i][0])的地址,即a[i]与&a[i][0]等价,即a[i]+j就是第i行第j列的数组元素a[i][j]的地址,即a[i]+j与&a[i][j]等价。所以对于二维数组a有以下关系:
(1)a[i]+j等价于&a[i][j]
(2)*(a[i]+j)等价于a[i][j]
根据一维数组与指针的关系可知:a[i]等价于*(a+i),进而可推出二维数组与指针的关系:
(1)*(a+i)+j等价于&a[i][j]
(2)*(*(a+i)+j)等价于a[i][j]
3.3 数组指针
数组指针:指向数组的指针。
int(*p)[i];
p++;
首先要明确优先级顺序:()>[]>*
(* p)[n]:根据优先级,先看括号内的,p是一个指针,指向一个一维数组,数组的长度是n,这是指向数组的指针,叫做数组指针。
指针的类型为int(*)[i],指针所指向的类型为int()[i],在运行p+1时,p所指向的地址在原有的地址上增加sizeof(int)*i。
3.4 指针数组
指针数组:装着指针的数组。
int* p[4];
p++;
*p[n]:根据优先级,先看[],则p是一个数组,再结合*,这个数组的元素是指针类型,共n个元素,是装着指针的数组,叫做指针数组。
指针数组++是指将指向指针数组第一个元素的指针向后移动一个元素的位置。具体来说,如果有一个指针数组p
,其中包含n个指针,每个指针指向不同的内存地址,那么p++
将把指针向后移动一个元素的位置,即指向p[1]
的指针。如果再执行p++
,则指针将指向p[2]
,以此类推。
3.5 指针数组和数组指针
指针数组和数组指针是两个不同的概念。
指针数组是一个数组,其中的每个元素都是一个指针,每个指针指向不同的内存地址。例如,`int *ptrArr[10]`定义了一个包含10个指向int类型的指针的数组,每个指针都可以指向不同的int类型变量。
数组指针是一个指针,它指向一个数组的首地址。例如,`int (*arrPtr)[10]`定义了一个指向包含10个int类型元素的数组的指针。在这种情况下,`arrPtr`指向的是整个数组,而不是数组中的一个元素。
因此,指针数组和数组指针的本质区别在于它们的类型不同,一个是数组,一个是指针。指针数组中的每个元素都是一个指针,而数组指针指向的是一个数组。在使用时需要根据具体情况选择合适的类型。
问题:指针数组名到底是数组名还是指针名?
指针数组名既可以被视为指针名,也可以被视为数组名,具体取决于它的上下文环境。
在指针数组作为函数参数传递时,指针数组名被视为指针名。因为在函数调用时,指针数组会被转换为指向其首元素的指针,并传递给函数。因此,指针数组名被视为指向数组首元素的指针名。
例如,下面的函数原型中,`ptrArr`被视为指向数组首元素的指针名:
void func(int *ptrArr[], int len);
在指针数组被定义时,指针数组名被视为数组名。因为指针数组本质上是一个数组,指针数组名表示整个数组。在定义指针数组时,需要指定数组的长度,这个长度就是数组的元素个数。
例如,下面的定义中,`ptrArr`被视为数组名:
int *ptrArr[10];
因此,指针数组名既可以被视为指针名,也可以被视为数组名,具体取决于它的上下文环境。
四.指针与字符串
4.1 字符数组
对字符数组的初始化有以下几种情形
(1)和其他数组一样,对字符数组逐个初始化
char str[5]={'a','b','c','d','\0'};
注意:c语言规定以字符'\0'作为字符串的结束标志。如果没有'\0',只能说数组str中存储了一串字符,但不能说str存储了字符串。
(2)用字符串直接初始化字符数组
char str[6]={"hello"};
char str[6]="hello";
编译器将双括号括起来的字符依次赋值给字符数组的各个元素,并自动在末尾补上字符结束标志字符'\0',一起存在数组str中,所以数组的个数要比字符串中字符的个数要多一个。在输出字符串时,末尾'\0'不输出,只用于判断字符串是否结束。
char str[]="hello";
按这种方式定义和初始化字符串数组,不必指定数组的大小,编译系统会根据字符串中字符的个数确定数组的大小。由于字符串常量"hello"的末尾字符时'\0',因此字符数组的长度为字符串的额中世纪的个数加1,即:字符数组str的长度为6。
4.2 字符指针
字符指针时指向字符型数据的指针变量。每个字符串在内存中都占用一段连续的存储空间,并有唯一的首地址。因此,只要把字符串的首地址赋值给字符指针,即可让字符指针指向字符串。
char* p;
p="hello";
char str[]="hello";
char* p;
p=str;
五.指针函数与函数指针
5.1 指针函数
1.指针函数的概念
指针函数是一种特殊类型的函数,其特点是其返回值的类型为某一类型的指针。也就是说,这种函数的返回值是一个地址,这个地址指向函数内部某种特定类型的变量或者数据结构。
2.指针函数的定义
指针类型 * (函数名称)(函数的参数列表类型)
3.指针函数的使用
#include <stdio.h>
int *find_max(int a[], int n) {
int *p = &a[0]; // 定义一个指向数组首元素的指针
for (int i = 1; i < n; i++) {
if (*p < a[i]) {
p = &a[i]; // 如果当前元素大于指针所指向的元素,则更新指针的值
}
}
return p; // 返回指向最大元素的指针
}
int main() {
int arr[] = {3, 5, 2, 7, 1};
int n = sizeof(arr) / sizeof(arr[0]);
int *max_ptr = find_max(arr, n); // 调用find_max函数,将最大元素的地址赋给max_ptr指针变量
printf("Max value is %d", *max_ptr); // 输出最大值
return 0;
}
5.2 函数指针
1.函数指针的概念
函数指针是一种特殊类型的指针,它指向函数而非变量。这种指针在内存中储存的是函数的入口地址,我们可以通过这个地址来调用相应的函数。
2.函数指针的定义
函数的返回值类型(*指针名)(函数的参数列表类型)
3.函数指针的使用
下面是一个函数指针的使用,首先定义一个max函数,然后在主函数内声明函数指针,再将max函数地址传递给函数指针,最后调用函数指针即可。
#include <stdio.h>
int max(int x, int y) {
return x > y ? x : y;
}
int main() {
int (*func_ptr)(int, int); // 声明一个函数指针变量
func_ptr = max; // 将函数max的地址赋给函数指针变量
printf("Max value is %d", func_ptr(10, 20)); // 输出最大值
return 0;
}