C语言指针

指针变量

内存是电脑上特别重要的储存器,计算机中程序的运行都是在内存中进行的。所以
未来有效的使用内存,就把内存划分成一个个小的内存单元。为了能够有效地访问
到内存的每个单元,就给内存单元进行了编号,这些编号就被成为该内存单元的地
址。

1.指针是内存中一个最小单元的编号,也就是地址
2.可以通过&(取地址操作符)取出变量的内存地址,可以把地址存放到一个变量中去,这个变量就是指针变量
3.平时口语中的指针,通常是指针变量,用来存放内存地址的变量(存放在指针中的值都被当做地址处理)

int num = 10;
int* p = # //p指向num的内存空间
//p用于存放num的地址,称之为指针变量
//int* 表示p是一个整型指针变量  &num表示num内存空间的地址
*p = 20//可以通过指针改变其所指向内存空间内的值的大小

指针大小

电脑内部都是实行2进制运算的,CPU一次处理数据的能力是2的倍数

对于32位的机器,有32根地址线,每根地址线可以通过正负电传递两种不同的信
息(1或者0)。(64位的机器同理)
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0001
………
1111 1111 1111 1111 1111 1111 1111 1111
显然32位下,一个指针变量的大小为4个字节(32个bit);64位下,为8个字节
(64个bit)

指针类型

指针定义的方式:type + *

char* p			int* p		short* p		long* p			
float* p		double* p

什么类型的指针决定了存放什么类型变量的地址

eg.char*类型的指针是为了存放char类型变量的地址

指针+(-)整数

指针的类型决定了指针向前或者向后一步的距离有多大
eg.
在这里插入图片描述
首先vs2019是小端储存,这里我另起草图,方便理解
在这里插入图片描述

草图中浅蓝色的参考线分隔除了数组a中,每一个元素的内存空间。
数组中的元素都是int类型,占4个字节。指针也是int类型,决定了指针的步伐
是4个字节大小。
同理,char类型的指针步伐是1个字节大小1

指针的解引用

指针的类型决定了,对指针解引用是时有多大权限
eg.char* 的指针解引用能访问1个字节;而int* 的指针解引用能访问4个字节

解引用操作符“*”的作用是引用指针指向的变量值,引用其实就是引用该变量的地
址(有点顺着网线去别人家的感觉)

野指针

1.指针未初始化
 	int *p;//局部变量指针未初始化,默认为随机值
	*p = 20;
	//也可以这样初始化:   int* p = NULL;
2.指针越界访问
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
    {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
    }
3.指针指向的空间释放

指针运算

指针±整数

在这里插入图片描述
在这里插入图片描述

草图中浅蓝色的参考线分隔除了数组a中,每一个元素的内存空间。
不难看出a[0]内存空间的地址是
0x0055FCEC~0x0055FCEF;a[1]内存空间的地址是
0x0055FCF0~0x0055FCF3;a[2]内存空间的地址是
0x0055FCF4~0x0055FCF7.
指针所指向的是内存空间的头部,解引用的时候,是从指针指向的空间开始,根据
指针类型读取对应的字节
eg.int* pa表示pa是int类型指针,访问权限是4个字节,那么会读取地址为
0x0055FCEC~0x0055FCEF的空间所对应的内容
*(pa + 1)会使得指针指向地址为0x0055FCF0的空间,而不是地址为
0x0055FCED的空间
同理*(pa + 1)会使得指针指向地址为0x0055FCF4的空间

指针-指针

在这里插入图片描述

*(pa + 6)表示pa指针根据自身类型向前走四步(pa是int类型指针,步伐是四个字节),即走24个字节指向0x00B3F700(数组中第七个元素的头部)。
然后在定义一个int类型指针pb指向*(pa + 6)所指向的内存空间
如下图所示:

在这里插入图片描述

此外还定义了num接收俩指针相减的结果,不难看出6指的是俩指针间的数组元素
个数

指针的关系运算

在这里插入图片描述

不难看出,这段代码利用for循环依次打印数组中前7个元素
for循环中的条件:
1.让pa指向a[0]所在的内存空间的头部
2.pa不能指向地址0x0093FACC对应的内存空间  的后面的空间 
(即pa只能指向地址为0x0093FAB4到0x0093FACC所对应的内存空间 )
3.循环一次后,pa根据自身类型往后走一步(4个字节)
PS:最后一次循环指针应该指向a[6]元素的内存空间的头部,并且根据自身权限
读取4个字节,也就是读出了数组中第七个元素

指针和数组

在这里插入图片描述

不难看出,数组名表示的是数组首元素地址
有两种情况除外:
	1.sizeof(数组名),这里数组名表示整个数组,计算整个数组大小
	2.&数组名,这里数组名也表示整个数组,取出的是整个数组的地址

用指针来访问数组:

p+i其实计算的是arr下标为i的地址

在这里插入图片描述

当几个指针指向同一个字符串的时候,它们实际会指向同一块内存空间;但是用
相同的字符串去初始化不同的数组,会开辟出不同的内存空间
#include<stdio.h>

int main()
{
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	if (p1 == p2)
	{
		printf("p1 == p2\n");
	}
	else
	{
		printf("p1 != p2\n");
	}
	if (arr1 == arr2)
	{
		printf("arr1 == arr2\n");
	}
	else
	{
		printf("arr1 != arr2\n");
	}
	return 0;
}//扔到编译器的结果是:p1 == p2  arr1 != arr2

二级指针

指针变量也是变量,是变量就有地址

CONST修饰指针

const修饰指针:

const int* p = &a;
int const *p = &a;
指针的指向可以修改,但是指针指向的内存空间内的值不能修改

const修饰常量:

int * const p = &a;
指针指向的内存空间内的值可以修改,但是指针的指向不能修改

const又修饰指针又修饰变量:

const int* const p = &a;
int const * const p = &a;
指针的指向和其指向的内存空间内的值都不可以修改

字符指针

字符指针通常用来存放字符类型的地址,可以通过指针来改变指针所指向的内存空
间的内容
int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'a';
    printf("%c\n", ch);//这里打印的结果自然是 a
    return 0; 
}
字符指针还可以存放字符串的首字符的地址(字符串在内存中是连续存放的)
当要打印首字符,直接解引用(p中存放的是字符串的首字符地址)
当要单个打印其他字符,可以通过指针+-整数来实现
当要打印字符串,给字符串首字符地址,编译器会自己打印‘\0’之前的内容

在这里插入图片描述

当定义一个char类型指针接收一个字符串的首字母地址,随后通过指针改变字符
串首字符的内存空间中的内容。不难看出,程序崩溃了。

在这里插入图片描述

常量字符串是不能修改了,为了保护代码,可以用const修饰
    const char* p = "graceful elegant charming";
    printf("%c\n", *p);
    printf("%s\n", p);
	return 0;

指针数组

与整型数组、字符数组进行类比,显然指针数组是存放指针的数组

int* arr1[10]//存放整型指针的数组
char* arr2[4]//存放一级字符指针的数组
char** arr3[5]//存放二级字符指针的数组
int main()
{
	int a = 10;		int b = 20;		int c = 30;
	int* arr[3] = { &a, &b, &c };
	for (int i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}//依次打印指针数组中每个地址对应的内存空间的内容
int main()
{
	int arr1[5] = { 1, 2, 3, 4, 5 };
	int arr2[5] = { 2, 3, 4, 5, 6 };
	int arr3[5] = { 3, 4, 5, 6, 7 };
	int* parr[3] = { arr1, arr2, arr3 };
	for (int i = 0; i < 3; i ++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);
			//这样写也可以  printf("%d", *(parr[i] + j));
		}
		printf("\n");
	}
	return 0;
}
//运行结果   
//		 1 2 3 4 5
//		 2 3 4 5 6 
//		 3 4 5 6 7
//有点二维数组的感觉

数组指针

数组指针其实是指针,与整型指针、字符指针进行类比。
整型指针:能够指向整型的指针。
字符指针:能够指向字符的指针。
显然数组指针是能够指向数组的指针

数组指针的定义

int (*p)[10];
把p去掉便是p的类型:int*[10]
p先要和*结合,表示p是一个指针变量,然后指向大小为10的整型数组
[]的优先级要高于*,所以要加上括号

&数组名与数组名

前面有说道:
数组名表示的是数组首元素地址
有两种情况除外:
	1.sizeof(数组名),这里数组名表示整个数组,计算整个数组大小
	2.&数组名,这里数组名也表示整个数组,取出的是整个数组的地址

在这里插入图片描述

不难看出arr和&arr的值是一样的,但是它们的意义不一样。
&arr表示的是整个数组的地址,而不是数组首元素的地址
上面的&arr的类型是:int(*)[10],是一种数组指针类型
&arr + 1表示向前走一步,步伐的大小是整个数组,所以跳过了整个数组

数组指针的使用

既然数组指针指向的是数组,那么数组指针中存放的应该是数组的地址
当然了对一维数组使用数组指针
void Print(int(*p)[10])
{
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(*p + i));
//*p相当于数组名,数组名又是首元素地址,所以*p就是&arr[10]
//所以(*p + i)是我想要的值的地址,前面加个*解引用操作符,然后才是我想要的值
	}
	printf("\n");
}

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    Print(&arr);
    return 0; }
对于函数print_arr1:
	整个形参数组接收

在这里插入图片描述

对于函数print_arr2:
	形参写成指针的形式
	数组名arr表示数组首元素地址
	二维数组的首元素是第一行的首元素
	如上图二维数组在内存中是连续存放的
	printf("%d", *(*(arr + i) + j)
		*(arr + i)相当于第i行的首元素的地址   ,也相当于第i行的数组名
		*(arr + i) + j表示第i行第j列元素的地址
		 *(*(arr + i) + j)表示第i行第j列元素	
#define _CRT_SECURE_NO_WARNIGNS 1

#include<stdio.h>

void print_arr1(int arr[3][5], int row, int col) {
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

void print_arr2(int(*arr)[5], int row, int col) 
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
		  //printf("%d", *(*(arr + i) + j);也可以这样写
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = {{ 1,2,3,4,5 },{6,7,8,9,10},{11,12,13,14,15} };
	print_arr1(arr, 3, 5);
	print_arr2(arr, 3, 5);
	return 0;
}

数组参数、指针参数

一维数组传参

一位数组传参的时候,传过去的数组首元素的地址,本质是创建一个指针去接收
形参部分不需要创建一个数组来接收,拿到了数组首元素地址,就可以往后遍历整个数组
所以一位数组传参的时候可以省略数组大小
void test(int arr[10])
{}
void test(int arr[])//创建形参的不会真实创建数组
{}
void test(int arr[100])//这种写法不推荐,虽然没错,但是会误导
{}
void test(int *arr)
{}
void test2(int *arr[])
{}
void test2(int *arr[20])
{}
void test2(int *arr[200])
{}
void test2(int **arr)//传过来的是首元素地址,类型是int*,是int*类型变量的地址(一级指针)
{}//一级指针的地址应该放到二级指针中去

int main()
{
 int arr[10] = {0};
 int *arr2[20] = {0};
 test(arr);
 test2(arr2);//arr2 是一个指针数组的数组名,还是一个int*类型的首元素地址
}
	

二维数组传参

二维数组传参,函数形参的设计只能省略第一个[]中的数字
对于二维数组可以不知道有多少行,但是必须得知道一行多少元素
二维数组在内存上是连续存放的,如果省略了一行元素的个数,那么从何得知第二行首元素	
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
void test(int (*arr)[5])
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

一级指针传参

一级指针(普通变量的地址)传过去一级指针接受
#include <stdio.h>
 void print(int *p, int sz) {
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
 }
 int main()
 { 
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 print(p, sz);
 return 0; }

二级指针传参

一级指针的地址用二级指针来接收
#include <stdio.h>
void test1(int** ptr)
{}
void test2(char (*p)[5])
{}
void test2(char (*p)[3][5])//这里传过来的是整个二维数组的地址
{
	*p;//这里解引用相当于得到的是arr2的数组名
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 int* arr[4];
 char arr2[3][5];
 test1(pp);
 test1(&p);
 test1(arr);
 test2(arr2);
 test3(&arr2)//这里传过去的是整个二维数组的地址
 return 0; }

函数指针

类比整型指针、浮点型指针、数组指针
整型指针是指向整型的指针			浮点型指针是指向浮点型的指针			数组指针是指向数组的指针
那么函数指针应该是指向函数的指针
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);//这里扔到我的编译器里,打印的是:	003F1253
 printf("%p\n", &test);//                      	003F1253
 //test 和&test都是函数的地址(与数组不同,不存在什么函数首元素)
 return 0; 
 }

函数地址的存放

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*padd)(int, int) = Add;//函数指针变量
	//这个函数指针的类型:int (*)(int, int)
}

函数指针的调用

在这里插入图片描述

不难看出,调用函数指针时,“*”可有可无。
在前面数组指针、整型指针、浮点型指针……中,对指针解引用时,都会用“*”,仿佛天经地义般。所以加上“*”,方便阅读。

为了模拟开机启动时的情景,我们必须设计出一个C语句,以显示调用子例程。经过一段时间的思考,我们最后得到的语句如下:
(*(void (*)())0)()
——《C陷阱与缺陷

(*(void (*)())0)();
//void(*)()是函数指针类型
//(void (*)())是强制转换类型
//(void (*)())0 对0进行强制类型转换  转换成(void (*)())的函数指针类型  意味着0的地址放着这个返回类型是void、无参的函数的地址
//*(void (*)())0 //对0这个地址进行解引用
(*(void (*)())0)()//0地址处所放的函数没有参数,所以后边给(),以表示不需要传参
void (*signal(int , void(*)(int)))(int);
//在操作符优先级中()高于*,所以‘signal’优先和()结合,说明signal是函数名
//void(*)(int)是一个函数指针类型
//signal这个函数中的两个参数类型分别是int 、 void(*)(int)
//将函数名signal和参数遮住:void (*)(int);
//"void (*" 、 ")(int)"表示中间的是这个函数的返回类型类型
//声明有一个函数名为signal、俩参数分别为int和void(*)(int)、返回值类型为int的函数

//对上述声明的简化
typedef void(* p_sp)(int);//对函数指针类型void(*)(int)重命名为:p_sp
p_sp signal(int, p_sp);

函数指针数组

数组是一个存放相同类型数据的储存空间,将函数的地址存放到数组中,便称之为函数指针数组

定义

int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];

用函数指针数组来实现计算器

void menu()
{
	printf("**************************************\n");
	printf("**********  1.Add      2.Sub**********\n");
	printf("**********  3.Mul      4.Div**********\n");
	printf("**************************************\n");
}

int Add(int a, int b)//加法
{
	return a + b;
}

int Sub(int a, int b)//减法
{
	return a - b;
}

int Mul(int a, int b)//乘法
{
	return a * b;
}

int Div(int a, int b)//除法
{
	return a / b;
}


#include<stdio.h>

int main()
{
	int x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
	int (*p[5])(int  x, int y) = { 0, Add, Sub, Mul, Div };
	while (1)
	{
		menu();//开始菜单
		scanf("%d", &input);//选择运算符
		if ((input >= 1) && (input <= 4))//判断是否非法输入
		{
			scanf("%d %d", &x, &y);//输入俩操作数
			ret = (*p[input])(x, y);//调用函数指针数组
			printf("%d\n", ret);//打印
		}
		else
		{
			printf("请重新输入\n");
		}
	}
}

指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针
void*pf)() = test;//函数指针f
int (*pf[5])() ;//函数指针数组
pf[1] = Add;
int (*(*ppf)[5])() = &pf;//指向函数指针数组pf的指针ppf

回调函数

即通过函数指针调用的函数。将函数的地址作为参数传递给另一个函数,当通过这个地址指向 这个地址对应的函数,称其为回调函数。回调函数不是由该函数的实现方直接调用,而是在特定事件或者条件发生时,由另外一方调用的

void Content()
{
	printf("graceful");
}
void Print(void (*p)())
{
	p();
}
int main()
{
	Print(Content);//这里没有直接调用Content函数,而是将Content函数传给Print函数
}

模拟实现qsort

使用qsort所需的库函数:
stdlib
qsort的四个参数:

待排序数组的起始位置
数组的元素个数
一个元素的字节大小
比较函数(自定义)这个函数的返回值是int类型。如果*p1应该排在*p2前面,返回值<0;如果*p1、*p2 一样大,谁排前面都行,返回值为 0;如果*p2应该排在*p1前面,返回值 >0

在这里插入图片描述

qsort函数的使用

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

void Compare(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);//升序
  //return (*(int*)p1 - *(int*)p2);//降序
}

int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), Compare);
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
	return 0;
}

void* 指针

用于接受任何类型的地址
正因为可以接受任何类型的地址,所以无法±整数(不知道访问字节的大小即向前或向后走一步的大小)
可以强制转换类型后,进行比较 、±整数

模拟实现(采用冒泡的方式)

#include<stdio.h>

#include<stdlib.h>

void Swap(void* p1, void* p2, int size)
{
	for (int i = 0; i < size; i++)
	{
		char temp = *((char*)p1 + i);
		*((char*)p1 + i) = *((char*)p2 + i);
		*((char*)p2 + i) = temp;
	}
}

void Compare(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}

void Bubble(void* base, int count, int size, int(*Compare)(void*, void*))
{
	for (int i = 0; i < count - 1; i++)
	{
		for (int j = 0; j < count - 1; j++)
		{
			if (Compare((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	Bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), Compare);
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
}
  • 8
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值