c语言指针(2万多字超详细、全面解析)(⑅•͈ᴗ•͈).:*♡

深入了解指针(一)

1.了解内存和地址

内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
⼀个字节空间⾥⾯能放8个⽐特位,一个比特位可以储存2进制的位1或者0。
1Byte = 8bit
1KB = 1024Byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
每个内存单元也都有⼀个编号,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫:指针。
内存单元的编号=地址=指针

2.指针变量和地址

2.1取地址操作符(&)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{  //利用&获取a的地址
	int a = 20;
	printf("%p", &a);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在不同的环境下地址也会不同。

2.2指针变量和解引⽤操作符(*)

通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:00FAFC00,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。

#include <stdio.h>
int main()
{
 int a = 20;
 int * pa = &a;//取出a的地址并存储到指针变量pa中
 
 return 0;

解引⽤操作符(*)。

#include<stdio.h>
int main()
{  //利用&获取a的地址
	int a = 20;
	int* p = &a;
	printf("%d\n", *p);
	*p = 10;
	printf("%d", *p);
	return 0;
}

在这里插入图片描述
p是p中存放的地址,改变p相当于改变a的变量。

3. 指针的大小

32位平台下地址是32个bit位(即4个字节)
64位平台下地址是64个bit位(即8个字节)

int main()
{
	printf("%zd\n", sizeof(int*));
	printf("%zd\n", sizeof(char*));
	printf("%zd\n", sizeof(float*));
	printf("%zd\n", sizeof(double*));
	printf("%zd\n", sizeof(short*));
	return 0;
}

X86环境输出结果:
在这里插入图片描述
X64环境输出结果:
在这里插入图片描述

4.指针变量类型的意义

其实指针类型是有特殊意义的,指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

int main()
{
	int a = 20;
	int* pi = &a;
	char* pc = (char *)&a;
	printf("%p\n", &a);
	printf("%p\n", pc);
	printf("%p\n", pc+1);
	printf("%p\n", pi);
	printf("%p\n", pi+1);
}

在这里插入图片描述

5. const修饰指针

5.1使用const修饰变量

x的本质还是变量,因为有const修饰,编译器在语法上不允许修改这个变量。
在这里插入图片描述

5.2当const修饰指针

const 放在*的左边:const int * p / int const * p;
/ 意思:表示指针指向的内容,不能通过指针来改变了。但是指针变量本身的值是可以改的。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	 int  n = 0;
	printf("n = %d\n", n);
	//const int * p = &n; int const *p=&n;
	/*p = 20;n=20;
	printf("n = %d\n", n);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
const 放在*的右边:int * const p;
意思:表示指针变量p本身不可以修改了,但是指针指向的内容是可以通过指针变量来改变的。

 #include<stdio.h>
int main()
{
	 int  n = 0;
	printf("n = %d\n", n);
	 int * const p = &n;
	// int num = 20;
	// p = &num;
	//printf("n = %d\n", num);
	 *p = 20;
	 printf("n = %d\n", n);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
当*左右都有const
const int * const p;
意思:指针变量p不能被修改,指针变量p指向的内容也不能被修改。

6. 指针运算

指针的基本运算有三种,分别是:
• 指针±整数
• 指针-指针
• 指针的关系运算

6.1指针±整数

指针+整数
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];//*p获取arr数组的首地址
	int s = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < s; i++)
	{
		printf("%d ", *p+i);
	}
	return 0;
}

在这里插入图片描述
指针-整数

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int s = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[s - 1];
	for (int i = 0; i <s; i++)
	{
		printf("%d ", *p-i );
	}
	return 0;
}

在这里插入图片描述

6.2指针-指针

指针之间的运算的前提是两个指针指向同一块空间
不能相加只能相减

指针-指针本质是地址-地址获取的绝对值是指针和指针之间的元素个数。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.3指针的关系运算

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//*p获取arr数组的首地址
	int s = sizeof(arr) / sizeof(arr[0]);
	while (p < &arr[s])
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

在这里插入图片描述

7. 野指针

7.1. 指针未初始化

在这里插入图片描述

7.2指针的越界

在这里插入图片描述

7.3 指针指向的空间释放

在这里插入图片描述

深入了解指针(二)

1-数组名的了解

首先单独获取arr的地址会发现与数组的首元素地址一样
数组名就是数组⾸元素(第⼀个元素)的地址。

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	printf("%p\n",arr);
	printf("%p", &arr[0]);
	return 0;
}

在这里插入图片描述其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:
1• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,
单位是字节
而在sizeof 中arr代表的是整个数组

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int n = sizeof(arr);
	printf("%d", n);
	return 0;
}

在这里插入图片描述

2• &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素
的地址是有区别的)

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	printf("%p\n", arr);
	printf("%p\n", &arr);
	printf("%p\n", arr+1);
	printf("%p\n", &arr+1);
	return 0;
}

在这里插入图片描述

2. 使⽤指针访问数组

了解清楚地址就可以轻松访问数组内容。
可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可以访问数组呢?

#include <stdio.h>
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for ( i = 0; i < sz; i++)
	{
		scanf("%d",p+i);//p+i可以换成&arr[i]
	}
	for ( i = 0; i < sz; i++)
	{
		printf("%d  ", *(p+i));//*(p+i)可以换成arr[i]
	}
	return 0;
}

在这里插入图片描述

3. ⼀维数组传参的本质

void szcc(int arr[])
{
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int a = 0; a < sz; a++)
	{
		printf("\n%d  ", arr[a]);
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int a = 0; a < sz; a++)
	{
		printf("%d  ", arr[a]);
	}
	szcc(arr);
	return 0;
}

在32位环境下
在这里插入图片描述

函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的

所以⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。


void test1(int arr[])//参数写成数组形式,本质上还是指针
{
printf("%d\n", sizeof(arr));
}
void test2(int* arr)//参数写成指针形式
{
	printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩
}

4. 冒泡排序

  void mppx(int arr[], int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = 0;j<n-i-1;j++)
		{
		if (arr[j] > arr[j + 1])
			{
				int temp=arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}
void print(int arr[], int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main() //冒泡排序
{
	int arr[] = { 10,4,5,6,8,7,9,1,2,3 };
	int n = sizeof(arr) / sizeof(arr[0]);
	mppx(arr, n);
	print(arr, n);
	return 0;
}

在这里插入图片描述
再优化一些
减少重复排序


 void mppx(int arr[], int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int flag = 0;
		for (int j = 0;j<n-i-1;j++)
		{
			if (arr[j] > arr[j + 1])
			{
				flag = 1;
				int temp=arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
		if (flag == 0)//这⼀趟没交换就说明已经有序,后续不需要排序了直接break跳出循环。
			break;
	}
	
}
void print(int arr[], int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main() //冒泡排序
{
	int arr[] = { 10,4,5,6,8,7,9,1,2,3 };
	int n = sizeof(arr) / sizeof(arr[0]);
	mppx(arr, n);
	print(arr, n);
	return 0;
}

5.二级指针

二级指针通俗来讲就是指针变量的地址

int main()
{
	int a = 10;
	int* p = &a;//p是指针变量,是一级指针变量
	int** pa = &p;//*p 是二级指针变量
	printf("%d", **pa);//输出为10
	return 0;
}

在这里插入图片描述
三级指针就是二级指针便的地址(int ***paa=&pa)
以此类推四、五……指针。

6.指针数组

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int* arr[] = { &a,&b,&c };//指针数组 或者{a,b,c}
	for (int i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}

在这里插入图片描述

7. 指针数组模拟⼆维数组


int main()
{
	int arr1[5] = { 0,1,2,3,4 };
	int arr2[5] = { 6,7,8,9,10 };
	int arr3[5] = { 11,12,13,14,15 };
	int* arr[3] = { arr1,arr2,arr3 };//||{&arr1,&arr2,&arr3}
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d  ", arr[i][j]);
		}
	}
	return 0;
}

在这里插入图片描述

深入了解指针(三)

1.字符指针变量

int main()
{
	char arr[] = "abcdef";
	char* p = arr;
	printf("%c\n", *p);
	const char* ps = "abcdef";//这⾥是把⼀个字符串放到ps指针变量里了吗?
	printf("%s\n", ps);
	printf("%c\n", arr[2]);
	return 0;
}

代码 const char* ps = “abcdef”; 特别容易让同学以为是把字符串 abcdef放到字符指针 ps ⾥了,但是本质是把字符串 abcdef. ⾸字符的地址放到了ps中。
在这里插入图片描述
这⾥arr3和arr4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域,当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以arr1和arr2不同,arr3和arr4相同。

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	const char *arr3= "abcdef";
	const char *arr4= "abcdef";
	if (arr1 == arr2)
		printf("arr1和arr2是内存相同的\n");
	else
		printf("arr1和arr2是内存不同的\n");
	if (arr3 == arr4)
		printf("arr3和arr4是内存相同的\n");
	else
		printf("arr3和arr4是内存不同的\n");
	return 0;
}

在这里插入图片描述

2.数组指针变量

2.1了解数组指针变量的概念

整形指针变量 ——变量——存放的是整形的地址
字符指针变量——变量——存放的是字符的地址
数组指针——变量——存放的是数组的地址

int (p)[5]
诠释: * 先与p结合 说明 p是一个指针变量 , [5]代表的是存放的是5个整形的数组,所以p是一个指针,指向数组,这就是一个数组指针。
注意:[]的优先级比
高所以要用()确保p先于*结合。

2.2数组指针变量初始化

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int arr[3] = { 0 };
	int(*p)[3] = &arr;//得到的是数组的地址
	return 0;
}

p与&arr的类型一样
在这里插入图片描述

3.二维数组传参的本质

二维数组传参,形参是数组时

void xs(int a[3][4], int b, int c)
{
	for (int i = 0; i < b; i++)
	{
		for (int j = 0; j < c; j++)
		{
			printf("%d ", a[i][j]);
		}
	}
}
int main()
{
	int arr[3][4] = { 1,2,3,4,5,6,7,8,9101112 };
	xs(arr, 3, 4);
	return 0;
}

根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [4] ,所以第⼀⾏的地址的类型就是数组指针类型 int(*)[4] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

void xs(int(*p)[4], int b, int c)
{
	for (int i = 0; i < b; i++)
	{
		for (int j = 0; j < c; j++)
		{
			printf("%d ", *(*(p+i)+j));
		}
	}
}
int main()
{
	int arr[3][4] = { 1,2,3,4,5,6,7,8,9 ,10,11,12};
	xs(arr, 3, 4);
	return 0;
}

4.函数指针变量

变量可以取地址、数组可以取地址、函数也可以取地址。
写一个简单函数来看看

int mul(int x, int y)
{
	return x * y;
}
int main()
{
	printf("%p\n", mul);
	printf("%p\n", &mul);
	return 0;
} 

在这里插入图片描述
函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名的⽅式获得函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。如下:

4.1 函数指针变量的创建


int (*p)(x, y) = mul;//x和y写上或者省略都是可以的

int —— p指向函数的返回类型
(*p)—— 函数指针变量名
(int x, int y)—— p指向函数的参数类型和个数的交代

4.2 函数指针变量的使⽤

int mul(int x, int y)
{
	return x * y;
}
int main()
{
	int (*p)(x, y) = mul;//x和y写上或者省略都是可以的
		int ret = (*p)(2, 4);
	printf("%d\n", ret);
	printf("%d\n", (*p)(2, 3));
	printf("%d\n", p(3, 4));
	return 0;
}

在这里插入图片描述

5.函数指针数组

存放函数指针的数组

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}

int main()
{
	int (*p1)(int x, int y) = add;
	int (*p2)(int x, int y) = sub;//在这里p1与p2的类型相同我们可以把其放在数组
	int (*p[4])(int, int) = { add,sub };//这里[4]代表函数指针数组大小可以存放4个函数指针。
	printf("%d\n",p[0](20, 30));
	printf("%d\n", p[1](20, 30));
	return 0;
}

在这里插入图片描述

6.转移表

函数指针数组的应用

首先使用数组的方式实现一个简易的计算功能

 void meau()
{
	printf("         开始选择        \n");
	printf("**** 1.add    2.sub  ****\n");
	printf("**** 3.mul    4.div  ****\n");
	printf("****    0.exit       ****\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;
}
void calc(int(*p)(int a, int b))
{
	int a = 0; int b = 0; int c = 0;
	printf("请输入两个整数进行运算\n");
	scanf("%d %d", &a, &b);
	c = p(a, b);
	printf("运算结果=%d\n", c);
}
int main()
{
	int input = 1;
do
 {
	int a = 0; int b = 0; int c = 0;
	meau();
	scanf("%d", &input);
	switch(input)
	{
	case 1:
		printf("请输入两个整数进行运算\n");
		scanf("%d %d", &a, &b);
		c = add(a, b);
		printf("运算结果=%d\n", c);
		
		break;
	case 2:
		printf("请输入两个整数进行运算\n");
		scanf("%d %d", &a, &b);
		c = sub(a, b);
		printf("运算结果=%d\n", c);
		break;
	case 3:
		printf("请输入两个整数进行运算\n");
		scanf("%d %d", &a, &b);
		c = mul(a, b);
		printf("运算结果=%d\n", c);
		break;
	case 4:
		printf("请输入两个整数进行运算\n");
		scanf("%d %d", &a, &b);
		c = div(a, b);
		printf("运算结果=%d\n", c);
	
		break;
	case 0:
		printf("退出计算\n"); 
		break;
	default :
		printf("选择错误请重新选择/n");	
		break;
	}
} while (input);
	return 0;
}

用函数指针数组的方式实现一个转移的效果



void meau()
{
	printf("         开始选择        \n");
	printf("**** 1.add    2.sub  ****\n");
	printf("**** 3.mul    4.div  ****\n");
	printf("****    0.exit       ****\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;
}
void calc(int(*p)(int a, int b))
{
	int a = 0; int b = 0; int c = 0;
	printf("请输入两个整数进行运算\n");
	scanf("%d %d", &a, &b);
	c = p(a, b);
	printf("运算结果=%d\n", c);
}
int main()
{
	int input = 1;
	do
	{
		meau();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(add);
			break;
		case 2:
			calc(sub);
			break;
		case 3:
			calc(mul);
			break;
		case 4:
			calc(div);
			break;
		case 0:
			printf("退出计算\n");
			break;
		default:
			printf("选择错误请重新选择/n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述

深入了解指针(四)

1.回调函数

回调函数就是⼀个通过函数指针调⽤的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。

在上面转移表的实现中已经使用的回调函数接下来给诠释一下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里列如 int add(int x,int y)——到void calc(int(*p)(int a, int b))——c=p(a,b);
这就是一次回调函数的实现。

2.qrost的使用

qrost—库函数—可以实现任意数据类型的快速排序。

void qsort(void* base, //base中存放的是待排序数组的第一个元素的地址
size_t num, //num存放的是base指向的数组中的元素个数
size_t size, //size是base指向的数组中一个元素的长度,单位是字节
int (compar)(const voidp1, const voidp2) //函数指针-指向了一个比较函数,这个比较函数是用来比较数组中的两个元素的
//如果p1指向的元素大于p2指向的元素,那么函数返回>0的数字
如果p1指向的元素等于p2指向的元素,那么函数返回0
如果p1指向的元素小于p2指向的元素,那么函数返回<0的数字 );
void
类型的指针不能解引用操作符,也不能+/-整数的操作
这种指针变量一般是用来存放地址的
使用之前要强制类型转换成想要的类型
char类型:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
void com(const void* p1, const void* p2)
{
	return (*(char*)p1 - *(char*)p2);
}
int main()
{     //qsort函数的实现
	char arr[] = "fd gas";
	size_t a = strlen(arr);
	qsort(arr, a, sizeof(arr[0]), com);
	for (int i = 0; i < a; i++)
	{
		printf("%c ", arr[i]);
	}
	return 0;
}

在这里插入图片描述
int 类型:

void com(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
int main()
{     //qsort函数的实现
	int arr[] = {2,6,8,4,5,3,1,7,9,10};
	size_t a = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, a, sizeof(arr[0]), com);
	for (int i = 0; i < a; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

在这里插入图片描述
结构体类型:

#include<stdio.h>
struct stu
{
	char name[15];//名字
	int  age;//年龄
};
int com_age(const void* p1, const void* p2)
{
	return (*(struct stu*)p1).age - (*(struct stu*)p2).age;
}
int main()
{     //qsort函数的实现
	struct stu arr[3] = { {"liuxin",18},{"zhangsan",20},{"wangwu",16} };
	size_t a = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, a, sizeof(arr[0]), com_age);
	for (int i = 0; i < a; i++)
	{
		printf("%d ",arr[i].age);
	}
	return 0;
}

在这里插入图片描述
结构体中名字的比较:

#include<string.h>
struct stu
{
	char name[15];//名字
	int  age;//年龄
};
//名字是字符串不能直接相减来比较
//使用strcmp函数来比较
//strcmp(字符串1,字符串2)
//如果字符串1>字符串2 返回>0   字符串1=字符串2返回=0      字符串1<字符串2返回<0
//strcmp从字符串的第一个字符开始比较  若第一个字符相等则向后推一个
char com_name(const void* p1, const void* p2)
{
	
	return strcmp( (*(struct stu*)p1).name,(*(struct stu*)p2).name);
}
int main()
{     //qsort函数的实现
	struct stu arr[3] = { {"liuxin",18},{"zhangsan",20},{"wangwu",16} };
	size_t a = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, a, sizeof(arr[0]), com_name);
	for (int i = 0; i < a; i++)
	{
		printf("%s ",arr[i].name);
	}
	return 0;
}

在这里插入图片描述

3.qrost函数的模拟实现

这个是int类型的想实现其他类型需要改cmp和打印的方式

int cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
void jh(char* e1, char* e2, size_t with)
{
	for (int i = 0; i < with; i++)
	{
		char a = *e1;
		*e1 = *e2;
		*e2 = a;
		*e1++;
		*e2++;
	}
}
void myself_qsort(void *base, size_t num,size_t with,int (*cmp)(const void *p1,const void *p2)) //这里可以看文章开头有解释
{

	for (int i = 0; i < num-1; i++)
	{
		for (int j = 0; j < num-1-i; j++)
		{

			if (cmp((char*)base+j*with,(char*)base+(j+1)*with)>0)
			{
				jh((char*)base + j * with, (char*)base + (j + 1)*with,with);
			}
		}
	}
}
int main()
{
	int arr[] = { 2,6,5,7,9,8,4,3,1,10 };
	size_t num = sizeof(arr) / sizeof(arr[0]);
	myself_qsort(arr, num, sizeof(arr[0]), cmp);

	for (int a = 0; a < num ; a++)
	{
		printf("%d  ", arr[a]);
	}
	return 0;
}

在这里插入图片描述

深入了解指针(五)

1. sizeof和strlen的对⽐

1.1 sizeof

在学习操作符的时候,我们学习了 sizeof , sizeof 计算变量所占内存内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。

 int main()
{
	int a = 0;
	printf("%d\n", sizeof a);
	printf("%d\n", sizeof (a));
	printf("%d\n", sizeof (int));
	return 0;

}

在这里插入图片描述

1.2 strlen

strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:
size_t strlen ( const char * str );
int main()
{
char arr1[] = { ‘a’,‘gh’,‘s’,‘f’};
char arr2[] = “abcefgh”;
printf(“%d\n”, strlen(arr1));
printf(“%d\n”, strlen (arr2));//strlen 需要包含#include

printf("%d\n", sizeof(arr1));
printf("%d\n", sizeof(arr2));

return 0;
}
在这里插入图片描述

1.3 sizeof和strlen的对⽐

sizeof:

  1. sizeof是操作符
  2. sizeof计算操作数所占内存的⼤⼩,单位是字节
  3. 不关注内存中存放什么数据

strlen:
4. strlen是库函数,使⽤需要包含头⽂件 string.h
5. srtlen是求字符串⻓度的,统计的是 \0 之前字符的个数
6. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界

2. 数组和指针笔试题分析

接下来的代码环境都是在32位下

2.1一维数组

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	    //数组名是数组首元素的地址
	//但是有两个例外:
	//1. sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
	//2. &数组名,数组名表示整个数组,取出的是整个数组的地址
  int a[] = { 1,2,3,4 };
  printf("%zd\n", sizeof(a));// 16  a代表数组名,数组中4个整数大小为4*4=16  
  printf("%zd\n", sizeof(a + 0));// 4/8  首元素地址 ,地址在32位下为8,64位下为4
  printf("%zd\n", sizeof(*a)); // 4 首元素大小
  printf("%zd\n", sizeof(a + 1));// 4/8 首元素地址+1
  printf("%zd\n", sizeof(a[1])); // 4   代表a[1]元素大小  
  printf("%zd\n", sizeof(&a));// 4/8  整个数组的地址还是地址
  printf("%zd\n", sizeof(*&a));//  16 整个数组大小
  printf("%zd\n", sizeof(&a + 1));// 4/8 (&a+1)代表跳过数组的下一个地址,还是地址所以还是4/8
  printf("%zd\n", sizeof(&a[0]));// 4/8 首元素地址
  printf("%zd\n", sizeof(&a[0] + 1));// 4/8 首元素地址+1(下一个地址)
	return 0;
}

%zd修饰size_t类型比%d更准确
在这里插入图片描述

2.2 字符数组

代码1:
//字符数组

	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", sizeof(arr));// 6  数组的大小6个字符大小为6
	printf("%zd\n", sizeof(arr + 0)); // 4/8  首地址大小
	printf("%zd\n", sizeof(*arr));// 1  arr是首元素的地址,*arr 就是首元素
	printf("%zd\n", sizeof(arr[1]));// 1 arr[1]这个元素大小
	printf("%zd\n", sizeof(&arr));// 4/8 数组地址
	printf("%zd\n", sizeof(&arr + 1));// 4/8 (&a+1)代表跳过数组的下一个地址,还是地址所以还是4/8
	printf("%zd\n", sizeof(&arr[0] + 1));//  4/8 &arr[0]+1是第二个元素的地址,是地址大小4/8个字节
	return 0;
}

在这里插入图片描述
代码2:

char arr[] = { 'a','b','c','d','e','f' };
printf("%zd\n", strlen(arr));// 随机值 没有标注\0
printf("%zd\n\n\n", strlen(arr + 0));// 随机值 首元素地址向后找到、0
//printf("%zd\n", strlen(*arr));// 报错 arr[1] 指的是'a' 在指的是97 streln会把97当作地址 向后统计字符串长度 97作为地址的空间,不一定属于当前程序
//printf("%zd\n", strlen(arr[1]));// 报错 'b'-98 也是非法访问
printf("%zd\n", strlen(&arr));// 随机值 &arr数组地址传给strlen向找\0
printf("%zd\n", strlen(&arr + 1));// 随机值  没有\0
printf("%zd\n", strlen(&arr[0] + 1)); // 随机值 

return 0;

在这里插入图片描述

在这里插入图片描述
代码 3:

char arr[] = "abcdef";
printf("%zd\n", sizeof(arr));// 7 字符串大小\0也算
printf("%zd\n", sizeof(arr + 0));// 4/8 首元素地址
printf("%zd\n", sizeof(*arr));// 1 首元素大小
printf("%zd\n", sizeof(arr[1]));// 1 'a'字符大小
printf("%zd\n", sizeof(&arr));// 4/8 
printf("%zd\n", sizeof(&arr + 1));// 4/8
printf("%zd\n", sizeof(&arr[0] + 1));// 4/8

代码4:

char arr[] = "abcdef";
printf("%zd\n", strlen(arr));// 6  \0之前的字符个数
printf("%zd\n", strlen(arr + 0));// 6 首地址地址向后找\0
printf("%zd\n", strlen(*arr));//  报错   'a'-97
printf("%zd\n", strlen(arr[1]));//报错 ’b'-98 
printf("%zd\n", strlen(&arr));//6 首地址向后找到\0
printf("%zd\n", strlen(&arr + 1));// 随机值 
printf("%zd\n", strlen(&arr[0] + 1));//5 

代码5:

char* p = "abcdef";
printf("%zd\n", sizeof(p));// 4/8  p是指针变量 计算的是指针变量的大小
printf("%zd\n", sizeof(p + 1));// 4/8 p+1是'b'的地址
printf("%zd\n", sizeof(*p)); // 1 *p-'a'
printf("%zd\n", sizeof(p[0]));// 1 把p[]当数组p[0]-'a'
printf("%zd\n", sizeof(&p));// 4/8 整个字符串地址
printf("%zd\n", sizeof(&p + 1));// 4/8
printf("%zd\n", sizeof(&p[0] + 1));// 4 / 8

在这里插入图片描述
代码6:

char* p = "abcdef";
printf("%zd\n", strlen(p));//6
printf("%zd\n", strlen(p + 1));//5 
printf("%zd\n", strlen(*p));//报错 *p-'a'
printf("%zd\n", strlen(p[0]));//报错 'a'
printf("%zd\n", strlen(&p));//随机值
printf("%zd\n", strlen(&p + 1));//随机值
printf("%zd\n", strlen(&p[0] + 1));//5

2.3 ⼆维数组

int a[3][4] = { 0 };
printf("%zd\n", sizeof(a));//48 整个数组
printf("%zd\n", sizeof(a[0][0]));//4
printf("%zd\n", sizeof(a[0]));//16   a[0][]一维数组,a[0]是第一行的数组名,现在单独放在sizeof内部,计算的是第一行的大小
printf("%zd\n", sizeof(a[0] + 1));// 4/8  地址
printf("%zd\n", sizeof(*(a[0] + 1)));//4  *(a[0] + 1) 是第一行第二个元素,4个字节
printf("%zd\n", sizeof(a + 1));// 4/8  首地址+1
printf("%zd\n", sizeof(*(a + 1)));// 16 a[1][]
printf("%zd\n", sizeof(&a[0] + 1));//4/8  &a[0]+1就是第二行的地址,是地址就是4/8个字节
printf("%zd\n", sizeof(*(&a[0] + 1)));//16
printf("%zd\n", sizeof(*a));//16
printf("%zd\n", sizeof(a[3]));//16 不存在越界,因为不会真实的访问内存,仅仅是通过类型推导就可以知道长度的

在这里插入图片描述

3. 指针运算笔试题分析

代码1:

    int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%zd,%zd", *(a + 1), *(ptr - 1));
	// *(a+1)--指向 数组第一2个元素 2
//(ptr-1)--强制类型转换int* -1 到了数组最后一个元素地址 ,所以*(ptr-1)=5

在这里插入图片描述
代码2:

//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;
int main() 
{   //0x16进制
	printf("%p\n", p + 0x1);//结构体20个字节 0x100014
	printf("%p\n", (unsigned long)p + 0x1);//unsigned long一个字节  0x100001
	printf("%p\n", (unsigned int*)p + 0x1);//0x100004
	return 0;
}

代码3:

int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);//1 注意,表达式的操作使用  (0,1)就是1
	return 0;
}

代码4:

在这里插入图片描述

//假设环境是x86环境,程序输出的结果是啥?

int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//%p内存中的补码以16进制的形式打印 -4的 源码 10000000 00000000 00000000 00000100
                                                                                             //     补码 11111111 11111111 11111111 11111100
return 0;                                                                                    //           F  F      F  F     F   F    F  C

代码5:

int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//  10 (&aa+1)第三行首地址 int*类型*(ptr1-1)- 5,
                                          //  5   第二行首地址 
return 0;            

代码6:

char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);// "at"  pa++ -*pa到了"at"
return 0;

代码7:
在这里插入图片描述

char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;     //注意++cpp会运算效果保留
printf("%s\n", **++cpp);//"POINT" **++cpp  -c+2-"POINT"
printf("%s\n", *-- * ++cpp + 3);//"ER" 再++cpp 到了c+1 - "NEW" 再(--)就到了 "ENTER" 再首元素+3 到了"ER"
printf("%s\n", *cpp[-2] + 3);//"ST" 接上面 使用*cpp[-2] 就到了c+3 - "FIRST" 再首元素+3 到了"ST"
printf("%s\n", cpp[-1][-1] + 1);//"EW"  第三个的运算没有产生后续影响 所以cpp[-1][-1]到了"NEW" 再首元素+1就是"EW"
return 0;

这就是指针系列2万多字的总篇章,喜欢的话可以点点关注、收藏、赞。
(⑅•͈ᴗ•͈).:*♡

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值