首页 > 其他分享 >C语言——指针

C语言——指针

时间:2024-04-01 22:31:09浏览次数:20  
标签:arr int void C语言 数组 函数指针 指针

地址是由物理的电线上产生的,能够标识唯一一个内存单元。在C语言中,地址也叫做指针。

在32位机器中,有32根地址线。地址是由32个0/1组成的二进制序列,也就是用4个字节来存储地址。

在64位机器中,有64根地址线。地址是由64个0/1组成的二进制序列,也就是用8个字节来存储地址。

指针类型

1. 指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)

type* p;
//说明p是指针变量
//type:说明p指向的对象的类型
//说明p解引用的时候访问的对象的大小是sizeof(type)

2. 指针类型还可以决定指针+1时的步长

  • 整型指针+1,跳过4个字节
  • 字符型指针+1,跳过1个字节
  • type* p  + n,跳过n*sizeof(type)个字节

野指针

野指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针的形成原因:

  1. 指针未初始化
  2. 指针越界访问
  3. 指针指向的空间释放

如何规避野指针:

  1. 指针初始化:明确知道指针类型时,应直接初始化;不知道指针初始化为什么值时,暂时初始化为NULL
  2. 小心指针越界
  3. 指针指向空间释放,及时置NULL
  4. 避免返回局部变量的地址
  5. 指针使用前检查有效性

指针运算

1. 指针 +- 整数

#define N_VALUES 5;
float values[N_VALUES];
float *vp;
for(vp=&values[0];vp<&values[N_VALUES]; )
{
     *vp++;
}

2. 指针 - 指针

指针 - 指针得到的数值的绝对值时指针和指针之间的元素个数

指针- 指针运算的前提是:两个指针指向同一块空间

int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9] - &arr[0]);  //9
	return 0;
}

3. 指针的关系运算

地址是有大小的,指针的关系运算就是在比较指针的大小

#define N_VALUES 5;
float values[N_VALUES];
float *vp;

for(vp = &values[N_VALUES]; vp > &values[0]; )
{
   *--vp = 0;
}

//简化后
for(vp = &values[N_VALUES - 1];vp >= &values[0]; vp-- )
{
     *vp = 0;
}
//应避免写成简化后的代码。因为C语言规定:
//允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较
//但是不允许与指向第一个元素之前的那个内存位置的指针进行比较

指针与数组

指针:指针变量的大小是4/8个字节,是专门用来存放地址的

数组:是一块连续的空间,可以存放一个或多个类型相同的数据

二者的联系

数组中,数组名就是数组首元素的地址,数组名==地址==指针

当我们知道数组首元素的地址时,因为数组又是连续存放的,所以可以通过指针来遍历访问数组

二级指针

二级指针是用来存放一级指针变量的地址的

int main()
{
	int a = 10;
	int* p = &a;
    //p是一级指针变量,指针变量也是变量,变量是在内存中开辟空间的,是变量就有地址

	int** pp = &p;
    //pp是二级指针变量,二级指针变量用来存放一级指针变量的地址 
	//int* 是在说明pp指向的是int*类型的变量
	//* 说明pp是指针变量

	*(*(pp)) = 100; //将100赋给了a
	printf("%d\n", a);  //100

	return 0;
}

指针数组

指针数组是存放指针的数组,数组的每个元素都是指针类型

int arr1[10]; //整型数组——存放整型的数组
char arr2[10];//字符数组——存放字符的数组
int* arr3[10];//指针数组——存放指针的数组
int main()
{
	int* arr1[10];//存放整型指针的数组
	char* arr2[10];//存放一级字符指针的数组
	char** arr3[10];//存放二级字符指针的数组

	return 0;
}

例:利用指针数组模拟二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[] = { arr1,arr2,arr3 };

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);  //模拟了一个二维数组,但实际上是三个分开的数组
		}
		printf("\n");
	}
	return 0;
}

数组指针

数组指针,是指向数组的指针,存放的是数组地址的指针变量

int (*p1)[10];  //数组指针
//*p1是指针,指向的是数组,所以*p1是数组指针变量

int* p2[10];  //指针数组

数组指针:是指针,是指向数组的指针

指针数组:是数组,是存放指针的数组

 类比一下整型指针和字符指针

  • 整型指针:指向整型变量的指针,存放整型变量地址的指针变量
  • 字符指针:指向字符变量的指针,存放字符变量地址的指针变量
  • 数组指针:指向数组的指针,存放数组地址的指针变量
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;  //数组的地址,存储到数组指针变量
	return 0;
}

函数指针

函数指针:就是指向函数的指针

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//类比数组指针
	/*int arr[10] = { 0 };
	int(*pa)[10] = &arr;*/

	printf("%p\n", &Add);
	printf("%p\n", Add);  //两种写法
	//函数名是函数的地址
	//&函数名也是函数的地址
	int (*pf)(int,int) = &Add;  //pf是函数指针变量
	//int (*)(int,int)是函数指针类型


	return 0;
}

函数名表示的是函数的地址。与数组不同的是,&函数名表示的也是函数的地址

数组名表示的是数组首元素的地址

有两个例外

  1. sizeof(数组名),这里的数组名表示的是整个数组
  2. &数组名,这里的数组名表示的也是整个数组

 函数指针数组

函数指针数组是数组,数组的每个元素都是函数指针变量

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
int main()
{
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;

	//函数指针数组
	int (*pfArr[4])(int, int) = { Add,Sub,Mul,Div };
	return 0;
}

指向函数指针数组的指针

int (*(*p)[4](int,int) = &pfArr;  //函数指针数组的地址
//p就是指向函数指针数组的指针

区分函数指针、函数指针数组以及指向函数指针数组的指针

void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	void (*pf)(const char*) = test;//pf是函数指针变量  pf是变量名

	void (*pfArr[10])(const char*);//pfArr是存放函数指针的数组   pfArr是数组名

	void (*(*p)[10])(const char*) = &pfArr;//P是指向函数指针数组的指针  p是变量名

	return 0;
}

回调函数

 回调函数就是一个通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

举个例子:标准库中有一个函数qsort,用来排序,需要包含头文件#include<stdlib.h>

qsort函数的特点:

  1. 使用快速排序的方式
  2. 适用于任意类型的数据
/*void qsort(void* base,  //指向了需要排序的数组的第一个元素
           size_t num,  //排序元素的个数
           size_t size,  //一个元素的大小,单位是字节
           int (*cmp)(const void*,const void*))  //函数指针类型—这个函数指针指向的函数,能够比较base指向数组中的两个元素*/

/*void* 的指针—无具体类型的指针
 void* 类型的指针可以接收任意类型的地址
这种类型的指针是不能直接解引用操作的
也不能直接进行指针运算*/

#include<stdlib.h>
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;  //控制升序或降序,当前为升序
	//return *(int*)p2 - *(int*)p1;   //当前为降序
}
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void test1()  //测试qsort排序整型数据
{
	int arr[10] = { 9,8,5,4,3,7,6,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort 默认是升序排序
	qsort(arr, sz, sizeof(int), cmp_int);
	print(arr, sz);
}
//测试qsort排序结构体数据
struct Stu
{
	char name[20];
	int age;
};
//按照年龄排序
int cmp_stu_by_age(const void* p1, const void* p2)  
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test2()   
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",16},{"wangwu",19}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	
}

//按照姓名排序
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test3()
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",16},{"wangwu",19} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);

}
int main()
{
	//test1();
	//test2();
	test3();
	return 0;
}

标签:arr,int,void,C语言,数组,函数指针,指针
From: https://blog.csdn.net/m0_64005051/article/details/137185068

相关文章

  • c语言程序实验——实验报告四
    c语言程序实验————实验报告四实验项目名称:实验报告2数据描述实验项目类型:验证性实验日期:2024年3月21日一、实验目的1.在熟练掌握if语句和switch语句的基础上,能灵活使用if语句和switch语句进行选择结构的程序设计2.学习调试程序二、实验硬、软件环境Windows......
  • 2_C语言分支与循环
    C语言分支与循环分支break与continueif与elseswitch练习题循环forwhiledowhile练习题分支break与continuebreak:使流程跳到循环体之外,接着执行循环体下面的语句其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。所以:while中的break是用于永久终......
  • C语言 指针
    目录1.指针是什么?2.指针和指针类型2.1指针的解引用2.2指针加减整数3.野指针3.1指针未初始化3.2指针越界访问4.指针运算4.1指针加减整数4.2指针减指针4.3指针的关系运算5.指针和数组例1 例26.二级指针7.指针数组8.结语1.指针是什么?把内存单一的编号就......
  • 每日一题:C语言经典例题之门禁系统
    题目描述毛毛最近要负责图书馆的管理工作,需要记录下明天读者的到访情况。每位读者有一个编号,每条记录用读者的编号来表示。给出读者的来访记录,得到每一条记录中的读者是第几次出现。输入输入的第1行包含一个整数n,表示涛涛的记录条数;第2行包含n个整数,依次表示涛涛的记录中每......
  • 菜鸟记录:c语言实现PAT甲级1010--Radix
    很长时间没做,忙于考研和实习,久违的的拾起了算法。做了很长时间,其实总体思路还是很简单的,但满分不知道为什么就是到不了,又因为网上很多答案包括柳神的都是c++,无法参透,姑且只能这样了。Givenapairofpositiveintegers,forexample,6and110,canthisequation6=110bet......
  • C语言中常用的文件操作
    本文将介绍常用的关于文件操作函数,如fopen,fclose,fread,fwrite,feek,ftell,rewind以及feof和ferror等文件操作操作函数,还介绍一些用于所有输入输出流的函数如fgetc,fputc,fgets,fputs,fprintf,fscanf等函数,还介绍了sscanf,sprintf函数,feof和ferror函数。最后介绍了文件文件缓......
  • 初识编译和链接(C语言)
    文章目录编译和链接翻译环境预处理编译汇编链接运行环境编译和链接编译和链接这两个大的过程构成了翻译环境。其实,在ANSIC的任何一种实现中,存在两个不同的环境。一个环境是翻译环境,另一个是执行环境。翻译环境中,源代码被转换为可执行的机器指令。执行环境中,代......
  • c语言实现扫雷游戏
    c语言实现扫雷游戏写在前面:呃呃呃其实是代码写完了才写的这篇文章,所以中间的测试就看不到(害,那么进入游戏首先应当是有一个菜单界面,供玩家选择开始游戏还是退出游戏,对吧菜单界面实现那么就写一个打印菜单的函数咯然后主函数部分调用一下menu(),再根据玩家的选择来判......
  • c语言例题,计算字符串长度,递归思想
    c语言中,计算字符串长度算是一个比较经典的题了,而今天我们运用两种不同的求解方法来写出不同的程序来实现计算字符串的功能。主函数 先看到主函数,主函数中设置了一串7个字符的字符串,而后面接下来定义了两个变量len1和len2,同时分别打印len1和len2,当然,打印的这两个变量其实就......
  • C语言每日一题
    1.题目2.分析这里考察的是运算符运算规则的问题,影响运算符求值顺序的主要是运算符的优先级和结合性。优先级:主要是不同优先级运算符之间的运算规则结合性:主要是优先级相同时运算符需要遵守的运算规则本题中的表达式,有以下4种运算符,它们是具有不同优先级的运算符,因此......