目录
指针
指针概念
要想明白指针,我们先要明白数据在内存中是如何存储的。
1.计算机的内存空间是由许多存储单元构成的,每一个存储单元代表着一个字节,每个存储单元对应一个唯一的编号,也就是地址。程序在运行时,数据就存放在这些存储单元内,使用数据时有两种方法,一是使用变量名直接读取,另外一种就是通过变量的地址来读取内容。
2.指针-即地址。我们把一个变量的地址称为该变量的指针。由于通过地址能够找到所需的变量单元,也就是说地址指向该变量单元,因此,将地址形象化地称为”指针“。
指针变量
指针变量:用于存放变量地址的变量
指针:变量的地址。
区分两者概念:例如”int i=0; i的地址是0x 11223344,int *p=&i;那么p就是指针变量,而0x 11223344就是变量i的指针。
定义指针变量
指针变量的一般定义形式:类型名 * 指针变量名
例如:int * pa ,int 表示pa所指向的数据类型是int类型,*表示变量为指针变量,pa是指针变量名
类型名:用于指定指针变量指向的变量类型
在c语言中数据是分类型的,不同的数据类型,内存所分配的字节数目和存储方式都是不同的(整数:补码形式,实数:指数形式),如果说只给定了地址,但是不知道数据所占空间大小和存储方式,是无法正确取出数据的。
例如:int *pa pa是指向int类型变量的指针变量,float *pa pa是指向float类型变量的指针变量
指针类型:指向整形数据的指针类型为int *,读作指向int的指针。指向字符型数据的指针类型为char *,读作指向char的指针。
例如:int *pa ,pa的类型是int *,int * 是pa的类型。float *pa ,pa的类型是float *,float* 是pa的类型。
从语法上看,只要把指针声明语句里的指针名去掉,剩下的部分就是指针的类型
例如:int *pa,指针类型是int *,int **p,指针的类型是int**,int (*p)[7],指针类型是int (*)[7],int *(*p)[7],指针类型是int *(*)[7]。
从语法上看,只要把指针声明里的指针名和名字左边的指针声明符去掉,就是指针所指向的类型
例如:int *pa,pa是指向int类型的指针变量 ,int **p,p是指向int *类型的指针变量,int (*p)[7],p是指向int ()[7]类型的指针变量,int *(*p)[7],p是指向int *()[7]类型的指针变量
#include<stdio.h>
int main()
{
int a=10, b=20;
int* pa, * pb;
pa = &a;
pb = &b;
printf("%d,%d\n", a, b);
printf("%d,%d", *pa ,* pb);
return 0;
}
引用指针变量
两种运算符 &(取地址运算符):取出变量地址,*(指针运算符),*p代表指针变量p指向的对象。
1.定义指针变量和a变量 int *p,a;
1.指针变量赋初值:p=&a;将a的地址赋给了指针变量p
2.赋值语句:*p=1,将1赋给了a,a=1
3.引用指针变量所指向的值 printf("%d",*p) 以输出整数形式输出指针变量所指向的值,即a的值。
将两个整数按照从小到大输出
#include<stdio.h>
int main()
{
int a, b, tmp;
int *pa, * pb, * p;
pa = &a;
pb = &b;
printf("输入两个数字\n");
scanf("%d%d", &a, &b);
if (*pa > *pb)
{
tmp = *pa;
*pa = *pb;
*pb = tmp; // 改变了a,b的值
//p = pa;
//pa = pb;
//pb = p;// a,b的值没有交换
}
printf("%d,%d\n", a, b);
printf("%d,%d", *pa ,* pb);
return 0;
}
指针变量作为函数参数
交换两个整数
#include<stdio.h>
void swap(int* pa, int* pb)
{
int t;
t = *pa;
*pa = *pb;
*pb = t;
// 传址调用,改变了形参,实参改变
}
int main()
{
int a, b;
int* pa = &a; int* pb = &b;
printf("输入两个数字\n");
scanf("%d%d", &a, &b);
swap(pa, pb);
printf("%d,%d\n", a, b);
printf("%d,%d\n", *pa, *pb);
return 0;
}
指针运算
我们以一维数组为例来探讨指针运算。
指针可以进行加减运算
在除了两种特殊情况(sizeof(数组名)和 &arr时数组名代表整个数组),其余情况数组名都表示首元素地址。
int arr[5]; 定义一个一维数组
int *p=&arr[0]; int *p=arr; 两个语句等价,都是将数组的首元素地址赋给p。
(1)如果p指向数组中的一个元素,那么 p+1指向同一数组中的下一个元素,p-1指向同一数组的上一个元素。注意:指针运算是指对应地址跳过相应类型的字节数,例如int型的指针,int *p,p+1是指p的地址加四个字节,得到了下一个元素的地址,char *p,p-1是指在p所对应的地址减去1个字节。
(2)如果说int *p=&arr[0];那么p+i对应的就是arr[i]的地址。
(3)*(p+i) 和*(arr+i) 和arr[i]等价
(4)如果说p1,p2都指向同一个数组的元素,那么p2-p1的值是(p2-p1)/数组元素类型的字节数(p2-p1:地址之差),得到的是两个地址之间的元素个数。例如int *p2=&arr[3],int *p1=&arr[1],那么p2-p1的结果是2。
int* p1 = &arr[1];
int* p2 = &arr[9];
printf("%d\n", p2 - p1); // 8 两个之间的元素个数
printf("%d\n", *p2 - *p1);//两个元素之差
注意:两个地址不能相加:例p1+p2是没有任何意义的。
打印数组的所有元素
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,20 };
int* p = arr;
int i = 0;
for (i=0;i<10;i++)
printf("%d ", *(p+i));
return 0;
}
(5)自加自减
*p++:由于*和++优先级处于一级,结合性从右往左,所以先++,后解引用。
所以*p++和*(p++)等价。
*(++p),先++,后解引用 int *p=arr; *(++p)=arr[1]
*p++ 等价*(p++) ,先解引用,后++ int *p=arr; *(p++)=arr[0]
++(*p)元素值+1 int *p=arr; ++(*p)=arr[0]+1
数组名做函数参数
#include<stdio.h>
void exchange(int * str,int n)
{
//int sz = sizeof(str) ; 4
for (int i=0;i<n;i++)
printf("%d ", *(str+i));
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
exchange(arr,sz);
return 0;
}
数组名作为函数参数时,在此情况下,数组名表示首元素地址,所以形参变量需要使用指针来接收,可以证明这一点的是,在所调用的函数内部使用sizeof来计算str的大小,所得的是指针大小。也就是在32位平台是4,64位平台是8。
二维数组的指针
我们设定一个二维数组。int arr[3][4],表示一个三行四列的二维数组,每一行又是一个一维数组。
所以说可以认为二维数组是数组的数组,即arr是由3个一维数组所构成的。
以二维数组的角度来看,arr代表二维数组首元素的地址,而现在的首元素是由第一行的所有元素构成的,所以说arr代表的是第一行的地址,arr+1代表的是第二行的地址,由于第一行是一维数组,一维数组的地址是首元素地址,所以说arr[0]是arr[0][0]的地址,arr[1] 代表arr[1][0]的地址。
那么,我们如何表示某一个元素的地址呢。arr[0]是一位数组名,那么第一行的第一个元素地址就可以用arr[0]+0来表示,arr[0]+2也表示arr[0][2]的地址。
arr[i]和*(arr+i)等价,那么arr[i][j]的值就是*(arr[i]+j),也等价于*(*(arr+i)+j)。
注意:二维数组*(arr+2)代表的是arr[2],也就是arr[2][0]的地址。 arr是数组名,表示第一行的地址,*arr等价于*(arr+0),等价于arr[0],得到的是首元素arr[0][0]的地址,而*arr[0],arr[0]是arr[0][0]的地址,解引用表示对arr[0][0]的地址解引用,得到的是arr[0][0]的值。
输出二维数组的元素
#include<stdio.h>
int main()
{
int arr[3][2] = { 1,3,4,2,5,9 };
int i = 0;
for (int k = 0; k < 3; k++)
{
for (i = 0; i < 2; i++)
{
printf("%d ", *(*(arr+k) + i));
}
}
return 0;
}
了解了这些,我们开始用指针指向二维数组。
我们可以定义p指针,p指针指向一个包含有m个元素的一维数组。
例如 int arr[4][8];四行八列的整形二维数组,定义数组指针来指向这一个二维数组,int (*p)[8]
由于[]的优先级比*高,所以说使用()来改变优先级。
数组指针:int (*p)[4]:(*p)表示p是一个指针变量,[4]表示指针变量指向一个一维数组,再与int 结合,表示数组里面的元素是int类型。也就是p是一个指向由整形数据所组成的数组的指针。就是说p是一个指针,指向的内容是一个int类型的数组。
指针数组:int *p[4],p先和[4]结合,表示p是一个数组,和*结合,表示数组里面的元素全部都是指针类型,int 表示数组所指向的内容是整形的,p是一个由返回类型是int的指针所构成的数组。就是说,p是一个数组,数组里面的元素是int *类型的指针。
输出二维数组的元素
#include<stdio.h>
int main()
{
int arr[3][2] = { 1,3,4,2,5,9 };
int i = 0;
int(*p)[2];//定义p指针变量指向一个包含有两个整形的一维数组
p = arr;//p指向二维数组的第0行
int k = 0;
for (k = 0; k < 3; k++)
{
for (int i=0;i<2;i++)
// printf("%d ", *(*(p + k) + i));
// printf("%d ", *(p[k] + i));
printf("%d ", p[k][i]);
}
return 0;
}
数组指针的传参
有三名同学,每一位同学有四门成绩,计算平均分,并查找学生成绩。
#include<stdio.h>
void average(int *p,int n) //指向arr的每一个元素
{
float sum = 0;
for (int i = 0; i < n; i++)
sum += *(p + i);
printf("平均成绩是");
printf("%lf\n", sum / n);
}
void search(int(*p)[4],int i)
{
for (int j = 0; j < 4; j++)
printf("%d ", *(p[i-1] + j));
}
int main()
{
int arr[3][4];
int(*p)[4] = arr;
int i = 0;
printf("请输入学生成绩;");
for (i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
scanf("%d", &arr[i][j]);
}
average(* arr,12);//传入arr[0][0]的地址
printf("输入想要查找的学生成绩:学生序号1~%d\n",3);
scanf("%d", &i);
search(p, i);//查找某一位同学的成绩 对行进行查找
return 0;
}
字符指针
#include<stdio.h>
int main()
{
char* p = "Hello World";
printf("%s", p);
return 0;
}
直接用字符串常量来初始化一个指针。c语言对于字符串常量按照字符数组来处理,但是我们现在没有定义字符数组的名字,所以说只可以用指针来引用。
对于字符指针变量的初始化,实际上把字符串首元素地址赋给了指针变量。
实现字符串复制
#include<stdio.h>
void string_copy(char *p, char *str)
{
/*while (*str)
{
*p = *str;
p++;
str++;
}
*p = '\0';*/
/*while (*str)
{
*p++ = *str++;
}
*p = '\0';*/
while (*p++ = *str++)
;
}
int main()
{
char arr[] = "Hello World";
char* p = arr;
char* str = "HI";
string_copy(p, str);
printf("%s", p);
return 0;
}
指向函数的指针
什么是函数指针:在程序中定义了一个函数,编译的时候会把函数的源代码转化为可执行代码并分配存储空间,这份空间有起始地址,函数名代表起始地址。
定义函数指针:int (*p)(int ,int),定义p是一个指向函数的指针变量,它可以指向函数类型为整形且具有两个整形参数的函数,这时指针的类型是int (*)(int int)。
通过函数指针来调用函数
#include<stdio.h>
int exchange(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("a=%d,b=%d\n", a, b);
int (*p)(int*, int*);//定义一个指向int类型且参数是int *,int*的函数的指针
p = exchange;//p指针指向函数
(*p)(&a, &b);//调用函数
printf("a=%d,b=%d", a, b);
return 0;
}
指针数组:一个数组里面的元素是指针。指针数组可以用来指向多个字符串。
将字符串按照从小到大输出
#include<stdio.h>
#include<string.h>
void sort(char** p)//使用冒泡排序,使得每一个指针所指向的内容发生改变
{
char* s;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3 - i; j++)//3
{
if ((strcmp(p[j], p[j + 1])) >0)
{
s = p[j];
p[j] = p[j + 1];
p[j + 1] = s;
}
}
}
}
void print(char ** p)
{
for (int i = 0; i < 4; i++)
{
printf("%s\n", p[i]);
}
}
int main()
{
char* p[4]={"base","hello","oo","hi"}; // 定义一个指针数组,其内的元素是指针
sort(p);
print(p);
return 0;
}
指向指针的指针
*p=&i;p是指向i的指针
**pp=&p;pp是指向p的指针
int **p,p和*结合,表示p是一个指针,再次和*结合,表示指针所指向的内容也是指针,int表示指针所指向的是一个int*类型的指针。
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;//p是指向arr的指针 p指向arr【0】
int** pp = &p;//pp是指向p的指针,pp指向arr【0】,*pp得到arr【0】的地址,*pp+1 得到arr【1】的地址
for (int i = 0; i < 10; i++)
{
printf("%d ", *(*pp + i));//打印数字
}
printf("\n");
for (int i = 0; i < 10; i++)
{
if( (*(*pp+i))%2==0)
printf("%d ", *(*pp + i));//打印偶数
}
return 0;
}
int *(*p(int))[3]: p和()结合,说明p是一个函数,int说明函数的参数是int,*表示函数返回的是一个指针,和【】结合,表示指针指向的是一个数组,再和*结合,说明数组的每一个元素都是指针,int表示指针的每一个元素类型是int ,也就是说p指向一个函数,函数参数为int,返回类型是一个指针,指针指向一个数组,这个数组里面的元素是int类型的指针。
野指针
1.局部指针变量没有初始化,默认随机值 int *p;p没有指向对象。
2.指针越界访问