文章目录
我们先来看这一段代码
直接访问:通过变量名访问的方式
间接访问:通过指针变量去寻找变量的地址
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个 字节 全部变成了 0
用 char* 的指针变量 只能访问 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.指针与数组
上面 我们知道了指针(地址)与指针变量的关系,接下来让我们来了解指针与数组
这里的指针其实是指针变量,( 我们一般习惯将指针变量说成指针 )
- 我们知道 指针变量存放 普通变量的地址,那能不能存放数组这种连续存储单元的地址呢,
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]));
(4,8) - 计算的是第二个元素的大小
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])为(4,8)
printf("%d\n",sizeof(&a[0]+1));
与上面同理
2.
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
此时 arr方框内值为6 sizeof(arr)值 为 6;
printf("%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,就是第一行第二个元素的地址
地址的大小为 4(32位)8(64位 )
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]。
而 指针数组的本质是一个数组,数组中的每个元素用来保存一个指针变量 如 intp[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+20)
14为16进制 = 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+1 为 0x10004
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*
这样 * 就只能 访问一个字节 我们知道 1 的 16进制 为 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所指向元素的后面。