有关指针的见解

  • 1初步了解指针 和 指针变量

我们先来看这一段代码
直接访问:通过变量名访问的方式
间接访问:通过指针变量去寻找变量的地址

int main()
{
	int x = 10;
	printf("x = %d \n",x);
	return 0;
}
 我们 为 x 赋了一个10,通过x 将 10打印出来 这样为直接访问
 
int main()
{
	int x = 10;
	int* p = &x;
	printf("x = %d \n",*p);
	return 0;
}
这里 我们 创建了一个指针变量,接收了x的地址,通过p 来找到 x 的地址 将 x地址存放的值打印出来

看完这段代码大家肯定疑问,啥是指针变量,啥是指针,啥是地址

其实在 c语言中 我们每个变量都会在内存中占用一定的内存单元,不同的类型占据的字节不同,
int 占 4个字节 char 占 1个字节 等等 为了正确 访问这些变量 我们为内存中的每个字节都编上了号码
就像门牌号一样。每个字节的编号是唯一的,可以根据编号准确的找到存储在某个字节的数据,这就是地址

我们知道地址就是内存 中特定的编号 , 接下来我们来说指针和指针变量

1. 指针

指针 其实没有啥的他就是地址,一个变量的地址就称为该变量的指针

2.指针变量

指针变量是专门用来存放另一变量地址(即指针)的变量称为指针变量,
简单来说 指针 是 地址 , 而 存放 地址的就是指针变量

这里用代码来解释
int a = 10;
int* p = NULL;
这里定义了一个 指针变量, p 前面有 * 说明是指针变量
p = &a;
用p 来接收 a的地址(指针) 

在上面我们 初步了解 了指针,指针变量 ,接下来 让我 继续揭开指针的面纱

2.指针和指针的类型

我们一开始学习c语言 都会先接触 数据类型,那么指针是否有类型呢,大小又是多少呢

int a = 0; 整形

int* p = NULL;// 整形指针变量,接收一个整形变量的地址

char a = 0;

char* p = NULL;// 字符指针变量,接收一个字符变量的地址

long a = 0;

long* p = NULL;//长整型指针变量,接收一个长整型变量的地址

float a = 0.0f;

float*p = NULL;//单精度浮点型指针变量,接收一个单精度类型变量的地址

double a = 0.0;

double* p = NULL;//双精度浮点型指针变量,接收一个双精度类型变量的地址

计算指针的大小
#include<stdio.h>

int main()
{
	
	char* pa = NULL;
	short* pb = NULL;
	int* pc = NULL;
	float* pd = NULL;
	double* pi = NULL;
	// sizeof 返回类型是无符号整形,unsigned int 
	printf("%zu\n", sizeof(pa));// 4 / 8
	printf("%zu\n", sizeof(pb));// 4 / 8
	printf("%zu\n", sizeof(pc));// 4 / 8
	printf("%zu\n", sizeof(pd));// 4 / 8
	printf("%zu\n", sizeof(pi));// 4 / 8
%zu 是 专门用来打印 sizeof 的返回值
	return 0;
}

指针类型 的意义

我们 知道指针的大小不是4就是8,编译器不为啥创建一个统一的 指针变量来存放指针的呢,还要为指针变量创建类型
#include<stdio.h>

#include<stdio.h>

int main()
{
	int a = 0X11223344;   // 16进制数字
			 //每一个16进制 能换成4个2进制位
	/*int* pa = &a;
	*pa = 0;*/
	char* pa = &a;
	*pa = 0;
	return 0;	
}
我们 可以先调试一下看到 *pa 改变了a 的值,4个 字节 全部变成了 0char* 的指针变量 只能访问 1个字节 所以 赋值 只能 将 44 改变 成 00
所以 我们得到指针类型的意义是 指针类型的差异 是决定他解引用访问字节的大小
结论 : 指针类型决定了指针在被解引用的时候访问几个字节
列如 int*  访问 4个字节
     char* 访问 1个字节
    其他类型 也是访问其类型的大小

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

下面是 运用char 类型的指针变量 存放 a的地址,并为 a赋值

在这里插入图片描述

在这里插入图片描述

指针类型第二个意义: 指针类型加一减一时跳过几个字节.
这里跳过的字节 其实也与指针的类型有关, 如果是int 加一 也就跳过一个整形类型大小,就是跳过 4个字节
其他类型 加 一 或 减 一 就是 加上或 减去 他类型的大小

在这里插入图片描述
可以看到 p + 1 后 为 p 指向的 arr[1] 赋值 20 可以看到直接跳过了 4个字节

教大家一个技巧 我们想知道啥类型得指针 只需要将 *p 去掉 看剩下来得就型

如 int* p 去掉 *p ----------- int 我们 很清楚得知道 为 int 类型
现在 相对 简单,不用这样也能清楚明白,当我们遇到 2级指针或更高级得 这种方法会非常实用

指针的大小,其实就是地址的大小,在不同位数的编译器下其大小 是不一样的
32位 大小为 4
64位 大小 8

3.空指针和野指针 void指针

1.空指针

概念 : 不指向任何对象的指针即为空指针,表示该指针没有指向任何内存单元,

空指针,一般 为 不晓得现在为指针赋啥值,先至于空避免野指针的出现
形式如下
int* pa = NULL;  NULL 的定义就是 0   ((void*)0)
int* pa = 0;

2.野指针

1.野指针 是指那些没有初始化的指针变量

#include <stdio.h>
int main()
{ 
p 没有初始化,就意味没有明确的指向
 int *p; 局部变量指针未初始化,默认为随机值 0Xcccccccc
    *p = 20; 非法访问内存,这里p就是野指针
 return 0; }
 

2.越界访问

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
   {
        当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
   }
    return 0; }

3.void 指针

void* p 空类型 指针
1.任何指针都能赋值给void指针,它就像一个垃圾桶一样,不管是啥类型都来者不拒,等后我们在快排中能更加了解他

void* p = NULL;
int x = 10;
int* pa = &x;
p = pa;//这里不需要强制类型转换
//将pa 指向的赋值给 void指针pa
如果要使用 p 这是需要强制类型转换
printf("%d",*(int*)p);

通过以上void 指针 我们更能了解到,内存中的地址,地址在内存是唯一的,不管为将地址赋给啥类型的指针变量,
地址都是不会改变,唯一变化的是指针变量访问的大小,因为void 类型的指针变量本身都不知道自己要访问几个字节
我们要使用它 就必须 强制类型转换让他清楚的知道自己要访问几个字节。

4.指针与数组

上面 我们知道了指针(地址)与指针变量的关系,接下来让我们来了解指针与数组

这里的指针其实是指针变量,( 我们一般习惯将指针变量说成指针 )

  1. 我们知道 指针变量存放 普通变量的地址,那能不能存放数组这种连续存储单元的地址呢,
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	arr 为数组首元素地址
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
		printf("%d ", *(p + i));
	}
	return 0;
}
通过以上代码,我们就能知道 指针 是能存储数组地址的

int main()
{
	int arr[10] = { 0 };
	int* p = &arr[4];
	这里我们将arr 第 5个元素的地址赋予了指针变量 p
	int i = 0;
	i条件改为 < 6 防止 越界访问
	for (i = 0; i <6 ; i++)
	{
		*(p + i) = i;
		printf("%d ", *(p + i));
	}
	return 0;
}

我们知道了 指针能 存放 数组 的地址,大家肯定对 *( p + i )产生疑问把, 为啥还能这样访问数组
在这里插入图片描述
我们可以看到 p + i , i 先从 0 开始,之前说过 ,指针类型的意义 加一 减一 ,移动的是 一个类型的大小,
所以 p + i 每次随着 i 的增加 p 指向 的地址也在改变,通过解引用符号( * ) 将此时地址所对应的值改变成 i
在打印出来 。

数组打印的三种方式

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	//arr 为数组首元素地址
		int i = 0;
	for (i = 0; i < 10; i++)
	{
			*(p + i) = i;
			printf("%d ", arr[i]);
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
		printf("%d ", arr[i]);
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
		printf("%d ", *(arr + i));
	}
	printf("\n");
	return 0;
}

大家可以尝试一下 , 看一下这 3种打印方式得出的结果是否相同,
方法 3 的原理其实与第二种 相同,arr 指向数组的首元素地址,让他加一减一,一样是移动类型大小的字节
让后解引用 打印它对应地址的值
*(arr + i) 赋值与*(p + i) 等价
arr[i] == *(arr + i) == *( p + i) == p[i];

接下来让我们进入到 指针与二维数组

其实 指针与二维数组,大部分的内容还是围绕着一维数组
有关二维数组的理解

int arr[4][3] = { 0 };
这里我们定义一个二维数组
我们知道二维数组 看做一个一维 数组,每个元素 又分别含有n个元素
这里 arr[4][3] 就可以看作 arr[0],arr[1],arr[2],arr[3]
 第一个元素  含有 arr[0][1] arr[0][1] arr[0][3] 三个元素,其他两个类似
 
接下来我们来看下面的代码
nt main()
{
	int arr[4][3] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr[0]);
	printf("%p\n", arr[0][0]);
	return 0;
}

在这里插入图片描述
这里 我们可以看出他们 的地址是一样的,说明 arr 还是指向首元素的地址,那么与一维数组指向首元素地址有啥区别呢

接下来我们来看这一段代码
int main()
{
	int arr[4][3] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr+1);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);
	printf("%p\n", &arr[0][0]);
	printf("%p\n", &arr[0][0]+1);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
这里看出 arr + 1 与&arr[0] +1 增加的字节相等 说明 ,arr 代表的 二维数组 首行 的 地址 ,此时arr 加 减 是 行 数 的改变

🍑 我们 现在知道了 二维数组 的 数组名代表了 首行地址 ,那么有没有指针来接收这一行的地址呢

答案是有的 ,他就是我们要学的数组指针

🍑 4.1数组指针

int(*p)[3];
定义一个数组指针
还记得 上面那个方法把 , 我们 将*p 去掉 剩下 int()[3] 说明是一个数组 ,数组元素的类型为int,所以 p是一个指向整形数组的指针

再来一个例子

int(*p)[10];
解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
指针,指向一个数组,叫数组指针。

这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合

知道了 数组指针,我们接下来 来使用一下数组指针

#include<stdio.h>

void print1(int arr[3][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
	}
	printf("\n");
}
arr 相当首行的地址,相当于一维数组的地址,可以用数组指针来接受
void print2(int(*p)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			这里*( p + i ) 是 拿出了第i行 的值,+ j 就是 找到这一行j元素的地址
			*(*( p + i ) + j )  通过找到 i 行j 列的元素地址,* 找到他们的值
			printf("%d ", *(*(p + i) + j));
		}
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print1(arr, 3, 5);
    数组名arr,表示首元素的地址
    但是二维数组的首元素是二维数组的第一行
    所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    可以数组指针来接收
	print2(arr, 3, 5);

	return 0;
}

以后我们想要 传一个 数组的地址可以考虑使用 数组指针来接收

4.2 指针与数组练习

知道了指针与数组 的关系 接下来我们来几道小练习 加深一下印象

指针和数组笔试题解析
第一部分

sizeof (数组名) -- 数组名表示整个数组 -- 计算的是整个数组的大小
& 数组名 - 数组名表示整个数组,取出的是整个数组的地址
除此之外,所有的数组名都是数组首元素的地址
int a[] = {1,2,3,4}// 4*4 = 16
1.
printf("%d\n",sizeof(a));  //16

printf("%d\n",sizeof(a+0));
 a+0 为第一个元素的地址,
sizeof(a+0)计算的是地址的大小 4/8

printf("%d\n",sizeof(*a));
a表示首元素地址
(*a)是数组的第一个元素,sizeof(*a)计算的是第一个元素大小。4

printf("%d\n",sizeof(a+1));
//a+1 是第二个元素的地址,sizeof(a+1)
计算的地址的大小(4 --32位平台,8 -- 64位平台)

printf("%d\n",sizeof(a[1]));48- 计算的是第二个元素的大小

printf("%d\n",sizeof(&a));4,8- &a虽然是数组的地址,但是也是地址,
sizeof(&a)计算的是一个地址的大小,地址的大小就是 4 / 8

printf("%d\n",sizeof(*&a));
&a -- int (*p)[4] = &a;
(p中存放数组的地址)解引用*p找到的是数组的地址
*p = *&a;就等于a 
*&一个找到该数组地址,一个取出该数组地址
可以相互抵消
sizeof(*&a) == sizeof(a)
所以为16

printf("%d\n",sizeof(&a+1));
&a+1 先是取出数组的地址 加一 跳过一个数组找到后面的地址
但是 还是地址所以为 (4,8)

printf("%d\n",sizeof(&a[0]));
a[0]为第一元素 &a[0] 取出第一个元素的地址 所以sizeof(&a[0])为(48printf("%d\n",sizeof(&a[0]+1));
与上面同理

2.
//字符数组
char arr[] = {'a','b','c','d','e','f'};

printf("%d\n", sizeof(arr));
此时 arr方框内值为6 sizeof(arr)值 为 6printf("%d\n", sizeof(arr+0));
指向第 一个元素地址为 4/8

printf("%d\n", sizeof(*arr));
数组名表示首元素地址,解引用就为'a' 所以为1

printf("%d\n", sizeof(arr[1]));
第二个元素 char 类型 所以为1

printf("%d\n", sizeof(&arr));
取出 数组的地址 地址 4/8

printf("%d\n", sizeof(&arr+1));
取出 数组地址 加一 为 跳过一个数组 
但是还是为地址所以 4/8

printf("%d\n", sizeof(&arr[0]+1));
取出了第一个元素的地址 加一 拿到了第二个元素的地址
地址的大小 4 / 8



头文件为: #include <string.h>
strlen()函数用来计算字符串的长度,
其原型为:
    unsigned int strlen (char *s);
strlen()用来计算指定的字符串s 的长度,不包括结束字符"\0"。 当数到'\0'是 结束



char arr[] = {'a','b','c','d','e','f'};

printf("%d\n", strlen(arr));
随机值 
arr 为首元素地址 从'a'开始数知道找到'\0'
结束 应为arr[]没有'\0'所以不会停止 为随机值

printf("%d\n", strlen(arr+0));
随机值
arr + 0 还是为第一个元素的首地址
所以与上面同理

printf("%d\n", strlen(*arr));
错误
*arr 为第一个元素 的值
strlen 会将 第一个元素当成地址
所以 会读取错误

printf("%d\n", strlen(arr[1]));
错误
arr[1] 为第二个元素的值 
与上面的同理

printf("%d\n", strlen(&arr));
随机值
&arr 取出的是 数组的在地址  指向第一个元素
strlen(&arr)从第一个元素往后 找'\0'
因为没有'\0'所以为随机值
printf("%d\n", strlen(&arr+1));
随机值 - 6

printf("%d\n", strlen(&arr[0]+1));
随机值 - 1
与上面同理只不过 从第2个元素开始数

在这里插入图片描述
上面都是 有关数组的 练习 接下来我们 来到指针的练习,一下有些题目未作出解释你能,自行补充吗?
加油挑战一下,

指针和数组笔试题解析
第二部分
char arr[] = "abcdef";
//[a b c d e f \0]
printf("%d\n", sizeof(arr));
7 
printf("%d\n", sizeof(arr+0));
4
printf("%d\n", sizeof(*arr));
1
printf("%d\n", sizeof(arr[1]));
1
printf("%d\n", sizeof(&arr));
4
printf("%d\n", sizeof(&arr+1));
4
printf("%d\n", sizeof(&arr[0]+1));
4

char arr[] = "abcdef";

printf("%d\n", strlen(arr));
6
printf("%d\n", strlen(arr+0));
6
printf("%d\n", strlen(*arr));
错误
printf("%d\n", strlen(arr[1]));
错误
printf("%d\n", strlen(&arr));
6
printf("%d\n", strlen(&arr+1));
随机值

printf("%d\n", strlen(&arr[0]+1));
5

char *p = "abcdef";

printf("%d\n", sizeof(p));
4
printf("%d\n", sizeof(p+1));
4
printf("%d\n", sizeof(*p));
1
printf("%d\n", sizeof(p[0]));
1
printf("%d\n", sizeof(&p));
4
printf("%d\n", sizeof(&p+1));
4
printf("%d\n", sizeof(&p[0]+1));
4

char *p = "abcdef";

printf("%d\n", strlen(p));
6

printf("%d\n", strlen(p+1));
5

printf("%d\n", strlen(*p));
错误--- *p == a  a的ASCII值为97
相当于 strlen(97) 会出现错误

printf("%d\n", strlen(p[0]));
错误
同理
printf("%d\n", strlen(&p));
随机值
 p里面放的地址
strlen完全不知道'\0'的位置,所以为随机值  

printf("%d\n", strlen(&p+1));
随机值
printf("%d\n", strlen(&p[0]+1));
5

在这里插入图片描述

//二维数组
int a[3][4] = {0};

printf("%d\n",sizeof(a));
12 个元素 每个元素 为int 型 所以大小为
12*4 = 48

printf("%d\n",sizeof(a[0][0]));
第一行第一个元素 4
计算的为第一行第一个元素 所占的字节大小

printf("%d\n",sizeof(a[0]));
sizeof(arr[0])相当于计算了第一行的4个元素大小
所以答案为 16   


printf("%d\n",sizeof(a[0]+1));
a[0] + 1
// a[0]相当于数组首元素地址(第一行首元素地址)
a[0]作为数组名并没有**单独**放在sizeof内部;
也没有取地址,所以a[0]就是第一行第一个元素地址
a[0]+1,就是第一行第二个元素的地址
地址的大小为 432位)864位 )

printf("%d\n",sizeof(*(a[0]+1)));
4
a[0]+1 为第一行第 2个元素地址
解引用 拿到这个元素 这个元素为int 值
所以为 4

printf("%d\n",sizeof(a+1));

4 - 解释:
a是 二维数组的数组名,并没有取地址
也没有单独放在sizeof内部,
所以a就表示数组首元素的地址
即:
第一行的地址
a+1 就是二维数组第二行的地址
地址就是 4个字节 

printf("%d\n",sizeof(*(a+1)));
16 - 解释:{*(a+1) == a[1]}/ a[1]
为二维数组的第二行;
a+1 是 第二行的地址,所以*(a+1)表示第二行
所以计算的是第二行的大小

printf("%d\n",sizeof(&a[0]+1));
4 - 解释:
a[0]为第一行的数组名
&a[0] 将第一行的地址取出
&a[0]+1 为第二行的地址
a+1<==>&a[0]+1
地址 所以为 4

printf("%d\n",sizeof(*(&a[0]+1)));
16
&a[0]+1 就是第二行的地址
*(&a[0]+1) 就是第二行,所以计算的第二行的地址

printf("%d\n",sizeof(*a));
16
a为第一行地址
*a 就是计算第一行的地址的元素
为16
*(a+0-->a[0]
a 作为二维数组的数组名,没&,没有单独放在sizeof内部
a就是首元素地址,即第一行的地址,所以*a就是第一行,计算的是第一行的大小

printf("%d\n",sizeof(a[3]));  a[3]sizeof 内部的表达式不参与运算
16 - 解释:
a[3]其实是第四行的数组名(如果有的话)
所以其实不存在,也能通过类型计算大小的


#include<stdio.h>
int main()
{
       short s = 5;
       int a = 4;
       printf("%d\n", sizeof(s = a + 6));
       //sizof 中 s = 10 但为short 占2个字节所以打印2
       //sizeof内部的表达式不参加运算
       printf("%d ", s);
       return 0;
}

5.指针数组

我们刚刚知道了数组指针, 现在 来了解 一下 指针数组。

数组指针  
int(*p)[3];

指针数组 ----  由指针变量构成的数组
int*P[3];

因为[] 的优先级 高于 * 所以 p 先与[] 结合 成为 数组,让后 在与 int* 结合,
此时数组 元素 就 全为指针变量,p[0] p[1] p[2]  

数组指针和指针数组这两个词语很容易混淆。数组指针是一个指针,其指向的数据类型由一个数组构成
(将数组作为一个数据类型来看待)如 int (p)[5]。
而 指针数组的本质是一个数组,数组中的每个元素用来保存一个指针变量 如 int
p[5]。

简单来说
数组指针 ----- 含有一个指针 指向 数组
指针数组 ------ 含有多个指针,构成一个数组

指针数组的使用

1.
#include<stdio.h>

int main()
{
	int num = 0;
	此时 month 的每个元素都代表一个指针,指向一个字符串
	char* month[12] = { "January","February","March" ,"April" ,"May" ,
		"June","July","August","September"," October","November","December" };
	printf("输入月份\n");
	while (scanf("%d", &num) != EOF)
	{
		if (num >= 1 && num <= 12)
		{
			printf("%s\n", month[num - 1]);
		}
		else
		{
			printf("输入错误,重新输入\n");
		}
	}
	return 0;
}

2. 运用指针数组模拟实现二维数组

#include<stdio.h>
int main()
{
	int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 2,3,4,5 };
	int arr3[4] = { 3,4,5,6 };
	int arr4[4] = { 4,5,6,7 };
	//数组名为首元素地址
	int* p[4] = { arr1,arr2,arr3,arr4 };
	int i = 0;
	int j = 0;
	ret 为没列 元素的个数
	int ret = sizeof(arr1) / sizeof(arr1[0]);
	sz 为行
	int sz = sizeof(p) / sizeof(p[0]);
	for (i = 0; i < sz; i++)
	{
		for (j = 0; j < ret; j++)
		{
			printf("%d ", p[i][j]);
			这里p[i][j] ->*(*(p+i)+j) 也行
		}
		printf("\n");
	}

	return 0;
}


在使用 指针数组 里面的第1个实例 有没有 同学 会由疑问 指针存放 的是 一串 字符串还是 将 字符串的首地址传进去了呢

那么让我们一同进入字符指针 学习

6.字符指针

我们知道指针类型里 有字符指针类型 char*
下面我们 来学习一下 他的使用

一般 情况下
int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0; 
}

另一种使用
int main()
{
    const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    return 0; 
}
 其实 这里 pstr 并没有将 字符串 "hello bit." 放进指针变量当中 而是将 首地址 放进了pstr中
 这样也解释的同 指针变量 本身就是 创建出来存放指针(地址)的,存放首元素地址能更好的访问和修改其内容。

下面 我们来使用字符指针

 有字符指串 a b 将 a 和 b  串联起来 b 在 a 的后面
 
 int main()
{
	char a[50];
	char b[50];
	char* str1 = NULL;
	char* str2 = NULL;
	printf("输入字符串a\n");
	gets(a);
	printf("输入字符串b\n");
	gets(b);
	str1 = a;
	str2 = b;
	while (*str1 != '\0')
	{
		str1++;
	}
	while (*str1 = *str2)// 这里 是 将*str2 赋值 给 *str1
	{       // 但 *str 为空时 *str1 = *str2 ,这样表达式 也为空
		 // 条件为假 不进入循环 ,赋值结束
		str1++;
		str2++;
	}
	printf("%s ", a);
	
}

知道 了 大部分指针类型 那让我们来点小练习

指针练习

//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1); //0x100014
因为 p 为结构体  结构体 Test 类型大小为 20 
所以 p+1 相当于跳过一个 结构体 (p+201416进制 = 1*16+4*16^0
printf("%p\n", (unsigned long)p + 0x1);//0x10004 或 0x10008
p被强制类型转换 为一个整形 整形 加一  就是加一
就为0x10001
如果 为 (unsigend long*)p 这时加一 是跳过一个类型长度
printf("%p\n", (unsigned int*)p + 0x1);//0x10004
被强制类型转换 为一个 (unsigned int*) 类型 
字节大小为 4 所以 加一相当于加4
p+10x10004
return 0;
}


int main()
{
   int a[4] = { 1, 2, 3, 4 };
   int *ptr1 = (int *)(&a + 1);
// 先拿出数组的 地址 加一 相当于跳过一个数组
//  本来 &a 的类型 为 int(*)[] 
//(int*)强制类型转换 赋予 ptr1

   int *ptr2 = (int *)((int)a + 1);
// 将a 强制类型转化为 1 个字节 加一相当与跳过一个字节
// 假如 机器为小端存储 所以解释如图
   printf( "%x,%x", ptr1[-1], *ptr2);
   return 0;
}

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

#include <stdio.h>
int main()
{
            // 逗号表达是 , 取最右边
   int a[3][2] = { (0, 1), (2, 3), (4, 5) };
            //       1       3       5
   int *p = NULL;
   p = a[0];  p 拿到首元素地址
   printf( "%d", p[0]);  p[0] 相当于 *(p+0) 等于 1 
return 0;
}

在这里插入图片描述

 
int main()
{
   int a[5][5];
   int(*p)[4];
   p = a; // a 的类型 为int(*)[5];
   printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
   return 0;
}

在这里插入图片描述

int main()
{
   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));
   return 0;
}

在这里插入图片描述


#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}

在这里插入图片描述
这里 pa 属于 二级指针 , 其实 二级指针 也非常简单理解 , 想一想 一级指针 是不是 存放地址,那么 二级指针不就是存放 一级指针的地址吗。

在这里插入图片描述

int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}

别看这么多多级 的指针, 弄来弄去 还不是指向 地址吗

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

补充 大小端存储

你有没有 对 int a = 0X11223344 在内除 中 存放 产生过疑问
为啥 是 44 33 22 11 呢
其实 这个 更编译器 的大小端存储 有关,那什么是 大小端呢

大端是高字节存放到内存的低地址,低字节存放到高地址
小端是高字节存放到内存的高地址,高字节存放到低地址

在这里插入图片描述

在这里插入图片描述

我们可以 用代码 来测试一下 自己的编译器 是采用 那种存储方式

int main()
{
	int a = 1;
     这里拿出 a 的地址 将他强制类型转化为char*
	 这样 * 就只能 访问一个字节 我们知道 116进制 为 00 00 00 01 
	 如果 是小端 就为 01 00 00 00  如果 是 大端 00 00 00 01
	 char* 访问一个字节  小端就为 1 大端就是 0 
	if (*(char*)&a == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
}

做完练习 我们是 否 有过思考 数组,变量 ,指针变量的地址 指针都能存储,那么 指针能否存储 函数的地址呢?

答案是 肯定的 接下来让我们 学习一下 指针函数吧

函数指针

#include<stdio.h>

void test()
{
	printf("ha ha");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	思考一下下面那个可以 存放test 的地址
	void (*p)();
	void* p();
}
指针数组 其实根 数组指针 有点像  (*p) p 先与 * 结合 说明是一个指针变量
在于 外面的void () 结合 说明指向一个函数 指向的函数 无参数返回类型为void

void* p()
p先于()结合说明是一个函数 函数 的参数 为空,返回类型 为void*

在这里插入图片描述
接下来让我看两个有趣的指针

有趣指针分析
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int)

在这里插入图片描述
在这里插入图片描述
这样拆开容易理解,但语法不允许这样书写
在这里插入图片描述
typedef - 对类型进行重定义
typedef void (pfun_t) ( int );
//对void(
)( int )的函数指针类型重命名为pfun_t
在这里插入图片描述

回调函数

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

2.前引冒泡排序函数

#include<stdio.h>
void maopao(int a[], int sz)//自己定义的冒泡排序函数
//只能排序整形数据
{
       int i = 0;
       int j = 0;
       for (i = 0; i < sz - 1; i++)
       {
              
              for (j = 0;j<sz-i-1;j++)
              {
                     int tmp = 0;
                     tmp = a[j];
                     a[j] = a[j + 1];
                     a[j + 1] = tmp;
              }
       }
}
void print(int a[], int sz)
{
       int i = 0;    
       for (i = 0; i < sz; i++)
       {
              printf("%d ", a[i]);
       }
       printf("\n");
}
int main()
{
       int a[] = {9,8,7,6,5,4,3,2,1,0 };
       int sz = sizeof(a) / sizeof(a[0]);
       print(a, sz);
       maopao(a, sz);
       print(a, sz);
}

这里 我 们发现 冒泡排序只能 排序 整形 变量 ,

3.qsort();//运用快速排序
整形数据
字符串数据
结构题数据

4.qsort 函数元素原型
void qsort(void* base, size_t num,size_size
// 函数指针 int (cmp)(const void,const void*)

base 中存放的是待排序数据中第一个对象地址
num 排序数据的为元素个数
size 排序数据中一个元素的大小,单位是字节
(*cmp) 是用来比较待排序数据中的2个元素的函数
( 返回一个整形 如果大于0 第一个元素大于第二个元素
返回一个小于0 的数字说明第一个元素小于第二个元素
等于0 说明第一个元素等于得二个元素 )

循环不变 ,只有 比较 方法有所差异, 交换方法有所差异

在这里插入图片描述

5.运用 函数qsrot() 
1.排整形数据
#include<stdio.h>
#include<stdlib.h>
int hanshu(void* e1, void* e2)
{
       // 因为 e1 和 e2 为空指针解引用(*e1,*e2)
       //编译器无法判断移动几个字节所以无法编译需先将
       //e1 e2 强制类型转换
       return *(int*)e1 - *(int*)e2;
       /*{
              如果返回一个正数 说明e1大于 e2
              返回一个负数 e1 小于 e2
              返回 0 说明 e1 等有 e2
       }*/
}
void print(int a[], int sz)
{
       int i = 0;
       for (i = 0; i < sz; i++)
       {
              printf("%d ",a[i]);
       }
       printf("\n");
}
int main()
{
       int a[] = { 9,8,7,6,5,4,3,2,1,0 };
       int sz = sizeof(a) / sizeof(a[0]);
       print(a, sz);
       qsort(a, sz, sizeof(a[0]), hanshu);
       print(a, sz);
       return 0;
}

2.排列结构体数据
#include<stdio.h>
#include<string.h>// 使用字符比较函数strcmp 的头文件
#include<stdlib.h>//使用qsort的头文件
//结构体数据
struct Stu
{
       char name[20];
       int age;
};
//按照年龄
int nianling(const void* e1, const void* e2)
{
       return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
    // return ((struct Stu*)e2)->age - ((struct Stu*)e1)->age;
    // 交换一下e1 和 e2 就完成了年纪的降序
}
//按照名字
int mingzi(const void* e1, const void* e2)
{
       return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void hanshu()
{
       struct Stu s[3] = { {"zhangsan",30} ,{"lisi",34},{"wangwu",20} };
       int sz = sizeof(s) / sizeof(s[0]);
       //按照年龄比较
       qsort(s, sz, sizeof(s[0]), nianling);
       //按照名字比较
       qsort(s, sz, sizeof(s[0]), mingzi);
}1.回调函数(qsort) 头文件为 <stdlib .h>
int main()
{
       hanshu();
       return 0;
}
1.函数实现
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
void Swap(char* buf1, char* buf2, int widt)
{
       // 一个字节一个字节交换
       // widt为元素大小,如整形 4个 字节就交换4次
       int i = 0;
       for (i = 0; i < widt; i++)
       {
              char* tmp = *buf1;
              *buf1 = *buf2;
              *buf2 = tmp;
              buf1++;//char类型每次自加向后一个字节
              buf2++;
       }
}
				// 这里的cmp 是函数指针 接收的是你自己 写的比较函数的地址
void maopao(void* s,int sz,int widt,int (*cmp)(const void*e1,const void*e2))
{
       int i = 0;
       for (i = 0; i < sz - 1; i++)
       {
              int j = 0;
              for (j = 0; j < sz - i - 1; j++)
              {
                     //两个元素比较,
                     //arr[j] 与 arr[j+1]
                     if (cmp((char*)s + j * widt, (char*)s + (j + 1) * widt) > 0)
                           //交换 应为不知道 使用者 交换的数据类型
                           // s 为数组的首地址, 因为 s 为空指针不知道访问几个字节
                           // 也不知道 使用者 提供的数据类型所以不适用
                           // s+j ,s+j+1;
                           //可以将 s强制类型转化为char*就可一访问一个字节
                           //然后让(char*)s 加上 宽度 widt *j, 就可以访问使用者提供类型的字节了
                           // cmp((char*)s+j*widt,(char*)s+(j+1)*widt)
                     {
                           //知道了元素的地址,后面开始交换
                           //交换也需要判断类型
                           //字符类型使用strcmp,使用自定意函数Swap()进行交换,
                           Swap((char*)s + j * widt, (char*)s + (j + 1) * widt,widt);//将元素地址传入,和元素大小
                           
                     }
                     
              }
       }
}
struct Stu
{
       char name[20];
       int age;
};
void bijiao(const void* e1, const void* e2)
{
       return  *(int*)e1 - *(int*)e2;
}
void print(int arr[],int sz)
{
       int i = 0;
       for (i = 0; i < sz; i++)
       {
              printf("%d ", arr[i]);
       }
       printf("\n");
}
void bijiao2(const void* e1, const void* e2)
{
       return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void bijiao3(const void* e1, const void* e2)
{
       return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test1()
{
       int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
       int sz = sizeof(arr) / sizeof(arr[0]);
       print(arr, sz);
       maopao(arr, sz, sizeof(arr[0]), bijiao);
       print(arr, sz);
}
void test2()
{
       struct Stu s[3] = { {"zhangsan",30},{"lisi",34},{"wangwu",20} };//初始化
       int sz = sizeof(s) / sizeof(s[0]);
       //按照年龄来排序
       //maopao(s, sz, sizeof(s[0]), bijiao2);
       //按照名字来排序
       maopao(s, sz, sizeof(s[0]), bijiao3);
}
int main()
{      
       //test1();
       test2();
       return 0;
}

结果
在这里插入图片描述
上面为: 排序整形数据

下面为: 排序结构数据
先按照年龄排序:
排序前

在这里插入图片描述
排序后
在这里插入图片描述

按照名字排序
在这里插入图片描述
在这里插入图片描述
如果要按升序排序,则cmp返回值小于0(< 0),那么a所指向元素会被排在b所指向元素的前面;
如果cmp返回值等于0(= 0),那么a所指向元素与b所指向元素的顺序不确定;
如果要按降序排序,则cmp返回在这里插入图片描述
值大于0(> 0),那么a所指向元素会被排在b所指向元素的后面。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值