目录
1.字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* —— (指向字符的指针)
字符指针的使用细节:
int main()
{
char ch = 'w';
char* pc = &ch;
const char* ps = "abcdef";//将字符串"abcdef"首字符a的地址放在了ps里面,而不是将字符串"abcdef"存在ps里面
//常量字符串不可修改
//const 修饰*ps 导致*ps不能被修改
printf("%c\n", *ps);//a
printf("%s\n", ps);//abcdef
//*ps = 'w'; //常量字符串不可以修改
char arr[] = "abcdef";
char* p = arr;
//此时"abcdef"在数组里面才能够被修改,"abcdef"在字符串里面不能被修改。
return 0;
}
练习一下:
#include <stdio.h>
int main()
{
char str1[] = "hello world";
char str2[] = "hello world";
const char* str3 = "hello world";
const char* str4 = "hello world";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
分析:
比较变量大小的逻辑
2.指针数组
指针数组的本质是数组,数组里面存的是指针,也就是说数组里面存的是地址。
int main()
{
//代码1
char* arr[5] = {"abcdef", "zhangsan", "hehe", "wangcai", "ruhua"};//存放指针的数组 - 指针数组
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%s\n", arr[i]);
}
//代码2
//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* arr[3] = {arr1, arr2, arr3};//指针数组
//int i = 0;
//int j = 0;
//for (i = 0; i < 3; i++)
//{
// for (j = 0; j < 5; j++)
// {
// printf("%d ", arr[i][j]);
// }
// printf("\n");
//}
return 0;
}
分析代码1:
分析代码2:
3.数组指针
3.1数组指针的定义
整型指针是指向整型的指针
字符指针是指向字符的指针
同理可得:
数组指针的本质是指针,是一个指向数组的指针
int main()
{
int a = 10;
int* p1 = &a;//p1是整型指针,存放的是整型a的地址
char ch = 'w';
char* p2 = &ch;//p2是字符指针,存放的是字符ch的地址
int arr[10] = {1,2,3,4,5};
int (* pa)[10] = &arr;//取出的是数组的地址存放到pa中,pa是数组指针变量
//*先修饰pa,说明pa是指针,然后*pa修饰[10],说明pa是数组指针,最后*(pa)[10]修饰int,说明pa是整型数组指针
//int(*)[10] -> 数组指针类型
//pa是整型数组指针,指向的是一个含有10个元素的整型数组,pa里面存的是这个整型数组的地址
return 0;
}
3.2&数组名VS数组名
int arr[10];
我们知道数组名是首元素地址,arr是数组名,也是arr[0]的地址。
那么&arr是谁的地址呢?(数组的地址)
调试以下代码,我们能得到规律
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);//打印的是数组首元素的地址
printf("%p\n", arr[0]);//打印的数组首元素的地址,数组的首元素地址+1是跳过一个元素
printf("%p\n", &arr);//打印的是数组的地址,数组的地址是从数组的首元素的地址开始的,数组的地址+1是跳过一个数组
return 0;
}
分析:
int main()
{
int arr[10] = {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);
printf("%p\n", &arr+1);
return 0;
}
分析:
总结:
整型指针是指向整型的指针 整型指针+1,跳过一个整型,四个字节
字符指针是指向字符的指针 字符指针+1,跳过一个字符,一个字节
数组指针是指向数组的指针 数组指针+1,跳过一个数组,具体跳过多少个字节,看这个数组是什么类型的数组,有几个元素,假如该数组有10个整型,那么数组指针+1跳过40个字节
总结:
数组名表示首元素的地址
但是有两个例外
1.sizeof(数组名):数组名单独放在sizeof()里面,这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
2.&数组名:这里的数组名表示整个数组的地址,这里取出来的是整个数组的地址
除了上述两个例外之外,我们见到的所有的数组名都表示的是首元素的地址
3.3数组指针的使用
void print2( int (*p)[10], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", (*p)[i]);//p拿到的是整个数组的地址,解引用之后 *p 就是数组名
//*&arr ==== *p
}
}
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
print2(&arr, sz);
return 0;
}
但是这样调用函数的参数很奇怪
数组指针一般不用于一维数组传参,更多的是应用于二维数组传参
对于二维数组,每一行是它的一个元素,也就是说二维数组的元素是一维数组,从某种意义上讲,二维数组本质上也是一维数组,只不过二维数组的元素,变成了一维数组
//二维数组传参
void print1(int (*p)[5], int r, int c)//p是一个数组指针,指向一个一维数组
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
//printf("%d ", (*(p + i))[j]);
printf("%d ", *(*(p + i)+j));
//p是数组指针,+1会跳过一个数组
//对p解引用会得到数组的首元素地址
//比如,arr1是一维数组的首元素地址 那么&arr1就是一位数组的地址 *&arr1就是首元素地址
//arr1[2] ====== *(arr1+2)
//所以有两种打印方法
//注意*和[]优先级的问题
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5,},{2,3,4,5,6,},{3,4,5,6,7} };
print1(arr, 3, 5);
return 0;
}
结果:
区分:
int arr[5];//arr是整型数组
int* parr1[10];//parr1是存放整型指针的数组,是整型指针数组
int(*parr2)[10];//parr2是数组指针,指向的是有10个整型元素的数组
int(*parr3[10])[5];//parr3是一个数组,有10个元素,每个元素是指针,指向的是带有5个整型元素的数组
//parr3是存放数组指针的数组
//判断是数组还是指针的方法,看parr3先和谁结合,先和[]结合的话,parr3就是数组,先和*结合dehua,parr3就是指针
//[]的优先级比*高
4.数组参数、指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
数组传参,形参可以是数组,也可以是指针。但是数组传参本质上是传递是一个地址。
4.1一维数组传参
#include <stdio.h>
void test(int arr[])//ok?//可以。数组传参的本质上传的是一个地址,因此元素可以省略
{}//形参是数组
void test(int arr[10])//ok?//可以
{}//形参是数组
void test(int* arr)//ok?//可以//建议使用这个
{}//形参是指针
void test2(int* arr[20])//ok?//可以 //20可以省略
{}//形参是数组
void test2(int** arr)//ok? //可以
{}//形参是指针
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
4.2二维数组传参
void test(int arr[3][5])//ok?//可行
{}//形参是数组
void test(int arr[][])//ok?//不行
{}
void test(int arr[][5])//ok?
{}//形参是数组
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
// 所以行能省,列不能省
//这样才方便运算。
void test(int* arr)//ok?//不行//二维数组的数组名表示的首元素的地址,是一行的地址,是一个数组指针
{}
void test(int* arr[5])//ok?//不行//需要指针接收,但是这里形参是数组
{}
void test(int(*arr)[5])//ok?//可行//建议使用这个
{}
void test(int** arr)//ok?//不行//传过去的是一级指针,不能用二级指针接收
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
4.3一级指针传参
#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]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test1(int* p)
{}
//test1函数能接收什么参数?
//1.整型变量的地址
//2.一级指针
//3.整型一维数组数组名
void test2(char* p)
{}
//test2函数能接收什么参数?
//1.字符或者字符串的地址
//2.一级字符指针
//3.字符一维数组的数组名
4.4二级指针传参
#include <stdio.h>
void test(int** ptr)//二级指针传参 用二级指针接收
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
void test(int** p)
{}
//1.可以传二级指针
//2.可以传一级指针的地址
//3.可以传指针数组的数组名
5.函数指针
函数指针是指向函数的指针
下面的代码仔细介绍了函数指针的创建、使用以及和函数的区别。
int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10] = {0};
printf("%p\n", &arr);//数组的地址
printf("%p\n", arr);//数组的首元素地址
//虽然值是一样的,但是意义完全不同
printf("%p\n", Add);//对函数来说,没有首元素的讲法,
printf("%p\n", &Add);//加上&和不加上&,没有什么区别,拿到的都是函数的地址
//pf是函数指针变量
int (*pf)(int x, int y) = Add;//pf是函数指针,指向一个函数,这个函数的参数是 int x和int y ,返回类型是int
//pf是指针----->(*pf)
//指向的是函数,函数的参数是int x和int y ----->(*pf)(int x,int y)
//函数的返回类型是int -------> int (*pf)(int x,int y)
//函数指针类型就写完了 是:int (*pf)(int x,int y)
//后续所有的函数指针类型,都能这样分析
//使用函数指针
int sum = Add(3, 5);
printf("%d\n", sum);//打印Add函数的返回值 是8
int sum1 = (*pf)(3, 5);//能够完成Add函数的功能
printf("%d\n", sum1);//打印Add函数的返回值 是8
//省略写法,此时pf==Add
int sum2 = pf(3, 5);//能够完成Add函数的功能
printf("%d\n", sum2);//打印Add函数的返回值 也是8
//(*pf)(3, 5) 这里的*是一个摆设,完全可以省略掉 得到pf(3,5)
return 0;
}
int test(const char* str, double x)
{
}
int main()
{
int (*pt)(const char*, double) = test;
int (*pt)(const char* a, double b) = &test;
//上述这两种函数指针的写法都是可行的,可以写参数,也可以不写参数,可以写取地址函数名,也可以直接写函数名
return 0;
}
1.两个有趣的代码
//两个有趣的代码
int main()
{
//1.
(*(void (*)())0)();//把0转换成函数的地址
//分析
//0是int类型
//1.(void (*)())0--把0强制类型转换成函数指针,也就是说0位置处指向了一个函数,这个函数的返回类型是void,没有参数
//2.*(void (*)())0 --解引用函数指针,拿到了这个函数的函数名
//3.(*(void (*)())0)()--调用这个函数,这个函数是没有参数的
//也就是把0直接转换成void (*)()的函数指针,然后再去调用0地址处的函数
//2.
void (*signal(int, void(*)(int)))(int);
//分析
//1.是一个函数声明
//2.声明的函数是signal
//3.signal函数的第一个参数是int,第二个参数是函数指针,指向的函数的参数是int,返回类型是void
//4.signal的返回类型是函数指针,这个函数指针指向的函数的参数是int,返回类型是void
//上述代码不利于阅读
//
//3.改写
void (*signal(int, void(*)(int)))(int);
//改写成:
typedef void(*pf_t)(int);//----将函数指针类型void(*)(int)重新定义为pf_t
pf_t signal(int, pf_t);//也是一个函数声明
//pf_t signal(int, pf_t)和void (*signal(int, void(*)(int)))(int)是完全等价的
return 0;
}
函数指针也是一种类型,和我们熟知的int char 一样都是类型
void(*)(int,int)是一种类型,是一种函数指针类型
6.函数指针数组
把函数的地址存到一个数组中,那这个数组就叫函数指针数组
//int *p ----整型指针
//int* arr[5]-----整型指针数组
//int (*pf)(int ,int )----函数指针
//int(*pfarr[5])(int ,int )-----函数指针数组
//分析
//pfarr先和[5]结合,说明pfarr是数组
//再和*结合说明是指针数组,
//数组里面存放的指针类型是-----函数指针类型的指针
函数指针数组的应用:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
//函数指针数组
//可以存多个【参数相同,返回类型相同的】函数的地址
int (*pfarr[2])(int, int) = { Add,Sub };
int ret = pfarr[0](2,3);
printf("%d\n", ret);//5
ret = pfarr[1](2, 3);
printf("%d\n", ret);//-1
return 0;
}
用函数指针数组实现计算器的加减乘除功能
//用函数指针数组实现的
//计算器
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;
}
void menu()
{
printf("***************************\n");
printf("***** 1.add 2. sub ****\n");
printf("***** 3.mul 4. div ****\n");
printf("***** 0.exit ****\n");
printf("***************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
//函数指针数组 - 转移表
int (*pfArr[])(int, int) = { 0, Add, Sub, Mul, Div };
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
break;
}
if (input >= 1 && input <= 4)
{
printf("请输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("%d\n", ret);
}
else
{
printf("选择错误\n");
}
} while (input);
}
7.指向函数指针数组的指针
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
//(*ppfunArr)----说明ppfunArr是一个指针
//(*ppfunArr)[5]----ppfunArr指向的是一个含有5个元素的数组
//元素的类型 是void (*)(const char*) 是函数指针
return 0;
}
8.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
用回调函数实现计算器的加减乘除
//使用回调函数完成计算器的加减乘除
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;
}
void menu()
{
printf("***************************\n");
printf("***** 1.add 2. sub ****\n");
printf("***** 3.mul 4. div ****\n");
printf("***** 0.exit ****\n");
printf("***************************\n");
}
void calc(int (*p)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = p(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);//Add是回调函数
break;
case 2:
calc(Sub);//Sub是回调函数
break;
case 3:
calc(Mul);//Mul是回调函数
break;
case 4:
calc(Div);//Div是回调函数
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
}
8.1冒泡排序
//bubble_sort 函数只能排序整型数据
void bubble_sort(int arr[], int sz)
{
int i = 0;
//趟数
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test1()
{
//冒泡排序
//对整型数据进行排序 - 排序为升序
int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
print(arr, sz);
}
int main()
{
test1();
return 0;
}
bubble_sort排序的数据类型是单一的,如果需要排序多种数据类型的数据,bubble_sort不再适用
8.2——qsort函数的使用
想要使用qosrt函数,使用者必须自己先定义一个比较函数。这个比较函数是qsort的回调函数
qsort是C语言的标准库,是用来排序的,实现qsort的底层逻辑是快速排序
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
函数含义:用来排序
参数:
base:待排序数据的起始地址
num:待排序数据的元素个数
size:待排序数据元素的大小(单位是字节)
int (*compar)(const void*,const void*):比较两个元素大小的函数指针(函数的地址,这个函数可以比较两个元素的大小)
compar的返回类型如下:
qsort返回值:没有
void*的指针,非常宽容,可以接收任意类型的地址,但是不能直接对void*的指针进行解引用操作,也不能进行加减整数的操作(因为void*的指针是无具体类型的指针,因此解引用不知道访问的数据到底是什么类型的,同理,也不知道每一次加减整数跳过多少个字节),因此void*的指针必须要强制类型转换成对应的类型才能使用解引用操作符。
1.用qsort排序一个数组
#include <stdlib.h>
#include <stdio.h>
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
cmp_int(const void* a, const void* b)
{
return *(int*)a - *(int*)b;
}
void test2()
{
//冒泡排序
//对整型数据进行排序 - 排序为升序
int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print(arr, sz);
}
int main()
{
test2();
return 0;
}
2.用qsort排序一个结构体
#include <stdlib.h>
#include<string.h>
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//测试qsort排序结构体数据
void test3()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 55}, {"wangwu", 40} };
//按照名字比较
int sz = sizeof(s) / sizeof(s[0]);//结构体有几个元素的计算方法
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);//名字的升序
//qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);//年龄的升序
}
int main()
{
test3();
return 0;
}
//请自行调试排序后结构体s的数据顺序
3.用冒泡排序的思想模拟实现qsort函数
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
//假设是小端存储
//交换一个未知数据类型的数据
//一个字节一个字节的交换
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort2(void* base, int sz, int width, 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 - 1 - i; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void test4()
{
int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort2(arr, sz, sizeof(arr[0]), cmp_int);
print(arr, sz);
}
void test5()
{
struct Stu s[] = { {"zhangsan", 20}, {"lisi", 55}, {"wangwu", 40} };
//按照名字比较
int sz = sizeof(s) / sizeof(s[0]);
//bubble_sort2(s, sz, sizeof(s[0]), cmp_stu_by_name);
bubble_sort2(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int main()
{
test4();
//test5();
//自己去调试test5
}
PS:
异或,同或等等二进制操作符都只能作用于整型,不适用于字符类型
9.指针和数组的深度理解(题目)
1.一维整型数组
//一维整型数组
//全部考虑在32位平台下
int main()
{
int a[] = { 1,2,3,4 };
//理解:不同数据类型的指针,要用对应的同类型的指针接收
//int* p = a;//a是数组名,是首元素的1(int)的地址,用整型指针p接收a
//int(*pp)[4] = &a;//&a符合两个特例,拿到的是数组的地址,数组的地址要用数组指针(pp)接收
printf("%d\n", sizeof(a));//16----a作为数组名单独放在sizeof内部,计算的是整个数组的大小,单位是字节
printf("%d\n", sizeof(a + 0));//4----a不是单独放在sizeof内部,也没有&,a就表示数组首元素的地址
printf("%d\n", sizeof(*a));//4--------a是首元素地址,*a就是首元素,为int类型,4个字节
//a---int *,*a-----int
printf("%d\n", sizeof(a + 1));//----4,a是首元素地址,a+1跳过一个整型,是第二个元素地址,sizeof(a+1)计算的地址的大小,是4个字节
printf("%d\n", sizeof(a[1]));//----4,计算的是第二个元素的大小,4个字节
printf("%d\n", sizeof(&a));//---4,&a取出的数组的地址,地址在32位下都是4个字节
printf("%d\n", sizeof(*&a));//16------指针类型决定了+-1跳过几个字节,也决定了解引用的时候访问多少个字节的数据
//对整型指针解引用,访问一个整型,对字符指针解引用,访问一个字符
//此时&a拿到了整个数组的地址,*&a是对整个数组解引用,访问整个数组
//因此是16
//*和&能够抵消,因此*&a====a
//sizeof(*&a)====sizeof(a)
printf("%d\n", sizeof(&a + 1));//4-----&a是数组的地址,&a+1跳过整个数组,但是&a+1还是地址,是地址就是4个字节
printf("%d\n", sizeof(&a[0]));//4----&a[0]是第一个元素的地址,是4个字节
printf("%d\n", sizeof(&a[0] + 1));//4----&a[0]是第一个元素的地址,&a[0] + 1是第二个元素的地址,是地址就是4个字节
return 0;
}
2.字符数组
//全部考虑在32位平台下实现
int main()
{
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
//sizeof是计算对象或者类型创建的对象所占的空间的字节,但不会去访问这块空间,只是去算一算对象的空间是多大
//字符的本质是ASCII值
printf("%d\n", sizeof(arr));//6-----arr是数组名,并且是单独放在zizeof()内部。计算的整个数组的长度,6个字节
printf("%d\n", sizeof(arr + 0));//4----arr是数组名,并非放在sizeof内部,因此,arr表示首元素地址,arr+0表示首元素地址,是地址就是4个字节
printf("%d\n", sizeof(*arr));//1---arr是数组名,不是单独放在zizeof内部,因此,arr是后元素地址,*arr是首元素,是1个字节
printf("%d\n", sizeof(arr[1]));//1---arr[1]是数组的第二个元素,是1个字节
printf("%d\n", sizeof(&arr));//4---arr是数组名,&arr取出的是数组的地址,是4个字节
printf("%d\n", sizeof(&arr + 1));//4----arr是数组名,&arr取出的是数组的地址,&arr+1跳过一个数组,&arr+1也是地址呀,是地址就是4个字节
printf("%d\n", sizeof(&arr[0] + 1)); //4----&arr[0]拿到的首元素的地址,&arr[0] + 1拿到的是第二个元素的地址,是地址就是4个字节
//strlen计算的字符串的长度,遇到\0结束,计算的长度不包括\0.
//字符串的结束标志是\0,ASCII码值是0
printf("%d\n", strlen(arr));//随机值-----arr是数组名,没有放在sizeof内部,也没有&arr,因此arr是首元素地址,strlen得到数组arr后,
//从首元素处开始计算字符串的长度,直到遇到\0结束,数组arr内存的后面是否有\0,在什么位置出现是位置的,因此strlen(arr)的值是随机的。
//strlen越界访问了不属于自己的空间,但是并没有去影响内存,只是去越界数了数,并没有去改变不属于自己的内存
printf("%d\n", strlen(arr + 0));//随机值---arr是数组首元素的的地址,arr + 0还是首元素地址,
printf("%d\n", strlen(*arr));//非法访问内存-----arr是首元素地址,*arr是首元素,就是a,ASCII值是97,那么strlen就把97当作地址去访问,这块地址是不允许访问的,程序会崩溃
printf("%d\n", strlen(arr[1]));//非法访问内存----arr[1]是第二个元素,就是b,ASCII值是98,那么strlen就把98当作地址去访问,这块地址是不允许访问的,程序会崩溃
printf("%d\n", strlen(&arr));//随机值---&arr拿到的数组的地址,数组的地址也是指向数组的起始地址,和首元素的地址的值是一样的,strlen(&arr)计算的也是随机值
printf("%d\n", strlen(&arr + 1));//随机值----&arr + 1拿到数组的地址后,再往后跳过数组的地址,拿到的也是随机值,只不过会比&arr的随机值少6
printf("%d\n", strlen(&arr[0] + 1));//随机值----&arr[0] + 1拿到的是第二个元素的地址,会比&arr拿到的随机值小1
char arr[] = "abcdef";
//arr里面有\0,在abcdef后面
//arr里面有7个元素,分别是a b c d e f \0
printf("%d\n", sizeof(arr));//---7
printf("%d\n", sizeof(arr + 0));//4-----arr+0表示的首元素的地址
printf("%d\n", sizeof(*arr));//1----arr是首元素的地址,*arr拿到的是首元素
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4----&arr拿到的是数组的地址,是地址就是4个字节
printf("%d\n", sizeof(&arr + 1));//4----&arr拿到的数组的地址,&arr+1跳过整个数组,也就是跳过7个字符(\0也在数组内部),&arr+1还是地址呀,是地址就是4ge字节
printf("%d\n", sizeof(&arr[0] + 1));//4---数组第二个元素的地址
printf("%d\n", strlen(arr));//6----arr是数组首元素地址,往后数,遇到\0结束,一共6个
printf("%d\n", strlen(arr + 0));//6-----arr+0还是首元素地址,往后数,遇到\0结束,一共6个
printf("%d\n", strlen(*arr));//非法访问-----*arr是首元素
printf("%d\n", strlen(arr[1]));//非法访问----同上
printf("%d\n", strlen(&arr));//6-----&arr拿到的是数组arr的地址,往后数,但是类型和srelen不匹配,会发生强制类型转换
//size_t strlen(const char* str) 是strlen的函数原型 &arr的类型是char(* )[7]
//和str的类型不匹配,但是在函数调用的时候是函数说了算
//因此,char(* )[7]会被强制类型转换成const char*类型
//不管你传什么类型的参数给strlen函数,函数都会以const char*类型去实现函数功能
printf("%d\n", strlen(&arr + 1));//随机值----&arr + 1跳过整个数组,不知道后面有没有 \0,因此strlen数到的是谁机制
printf("%d\n", strlen(&arr[0] + 1));//5----&arr[0] + 1拿到的是第二个元素的地址,strlen往后数,有5个。
const char* p = "abcdef";
//p里面存的是a的地址
//"abcdef"里面存的是 a b c d e f \0
printf("%d\n", sizeof(p)); //4----p里面存的是字符a的地址,p是地址,p指向字符a,是地址就是4个字节
printf("%d\n", sizeof(p + 1));//4---p+1是地址,p+1指向字符b,是地址就是4个字节
printf("%d\n", sizeof(*p));//1----p是char*类型的变量,*p只能访问一个字节,就是访问字符a
printf("%d\n", sizeof(p[0]));//1----p[0]====*(p+0),也就是拿到第一个元素字符a,是1个字节
//总结:*p====*(p+0)====p[0]
// *(p+2)====p[2]
printf("%d\n", sizeof(&p));//4----&p拿到的是一级指针p的地址
printf("%d\n", sizeof(&p + 1));//4----&p拿到的是一级指针p的地址,&p + 1 跳过的是p(p里面存的是地址),那就是跳过了4个字节
printf("%d\n", sizeof(&p[0] + 1)); //4-----&p[0] + 1是字符b的地址
printf("%d\n", strlen(p));//6----p存的是字符a的地址,strlen从a的地址处往后数字符,遇到\0结束
printf("%d\n", strlen(p + 1));//5----p + 1存的是字符b的地址,strlen从b的地址处往后数字符,遇到\0结束
printf("%d\n", strlen(*p));//非法访问---*p是字符a,但是strlen要接收的是地址,a的ASCII值是97,当成地址传给strlen,97地址处,是不允许访问的,程序崩溃
printf("%d\n", strlen(p[0]));//非法访问---p[0]也是字符a,但是strlen要接收的是地址,a的ASCII值是97,当成地址传给strlen,97地址处,是不允许访问的,程序崩溃
printf("%d\n", strlen(&p));//随机值---&p取出的是p的地址,那么strlen从这个地址,往后访问,不知道有没有\0
printf("%d\n", strlen(&p + 1)); //随机值----&p拿到的是一级指针p的地址,& p + 1 跳过的是p(p里面存的是地址),那么strlen从这个地址,往后访问,不知道有没有\0
//此随机值和上一个的随机值是不存在数量关系的,因为& p + 1 跳过的p,不知道跳过的p里面含不含有\0,如果有的话,那么就不存在数量关系。
printf("%d\n", strlen(&p[0] + 1));//5----&p[0] + 1是字符b的地址,strlen从b的地址处往后数字符,遇到\0结束
return 0;
}
3.二维数组
int main()
{
//只考虑在32位下实现
//二维数组
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//48----二维数组也是数组,a是数组名。a单独放在sizeof里面,因此,计算的是数组的总大小,单位是字节
printf("%d\n", sizeof(a[0][0]));//4----a[0][0]是一个整型元素大小,是4个字节
printf("%d\n", sizeof(a[0]));//16----把二维数组的每一行看作一维数组的时候,a[0]是第一行的数组名,数组名单独放在sizeof内部,计算的是第一行的总大小,含有4个整型,因此a[0]是16个字节
//二维数组的每一行是二维数的一个元素。
printf("%d\n", sizeof(a[0] + 1));//难点
//4----a[0]虽然是二维数组第一行的数组名(并非二维数组的数组名),并非单独放在sizeof的内部,此时a[0]作为第一行的数组名并非表示整个第一行这个数组
//此时,a[0]表示的就是这一行代表的数组的首元素的地址,相当于&a[0][0]--->int*
//a[0] + 1,跳过一个整型,是a[0][1]的地址
printf("%d\n", sizeof(*(a[0] + 1)));//4----a[0] + 1是a[0][1]的地址,因此*(a[0] + 1)就是访问a[0][1],是4个字节
printf("%d\n", sizeof(a + 1));//4----a是二维数组的数组名,并非单独放在sizeof的内部,也没有&,因此a表示二维数组首元素的地址
// 对于二维数组,第一行是它的首元素,第二行是它的第二个元素
//a就是第一行的地址,a的类型是 int(*)[4] 此时a====&a[0],a+1====&a[1].a+2====&a[2]
//a+1就是第二行的地址,是地址的话,大小就是4个字节
printf("%d\n", sizeof(*(a + 1)));//16----a+1是第二行的地址,是数组指针,*(a + 1)对数组指针解引用,找到的就是第二行,因此计算的就是第二行的大小
printf("%d\n", sizeof(&a[0] + 1)); //4---a[0]是第一行数组名,&a[0]拿到的是第一行的地址,&a[0]+1,就是第二行的地址。是地址就是4个字节
printf("%d\n", sizeof(*(&a[0] + 1)));//16----&a[0] + 1是第二行的地址,*(&a[0] + 1)访问的是整个第二行
//*(&a[0] + 1)====a[1]
printf("%d\n", sizeof(*a));//16---a是数组名,不是单独放在sizeof内部,也没有&,a表示的是首元素的地址,也就是第一行的地址,*a就是访问整个第一行,大小就是16个字节
printf("%d\n", sizeof(a[3]));//16---代码没问题
//任何一个表达式都有两个属性
//比如 3+5
//值属性 8
//类型属性 int
//因此能够分析出a[3]的类型是int [4]
//因此并不需要访问a[3]
//只需要知道a[3]的数据类型就能确定a[3]的大小
//因此是16个字节
return 0;
}
4.证明sizeof里的表达式不会计算
int main()
{
short num = 20;
int a = 5;
printf("%d\n", sizeof(num = a + 1));//2
printf("%d\n", num);//20
//如果num = a + 1发生了计算,那么num的值是6,但是此时num的值还是20,说明num = a + 1没有计算
//那么为什么没有计算呢?
//sizeof是计算空间大小的,那么从num = a + 1,可以直到num的类型是short,是2个字节
//算不算num = a + 1,sizeof计算的空间大小都是2个字节
//因此就没必要算num = a + 1
return 0;
}
10.指针的深度理解(题目)
练习1:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d %d", *(a + 1), *(ptr - 1));//2 5
return 0;
}
//程序的结果是什么?
//2 5
分析:
练习2:
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);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
分析:
练习3
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);//4,2000000
//%x---打印的16进制整数
return 0;
}
分析:
练习4
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//(0, 1), (2, 3), (4, 5)都是逗号表达式
//本质上 a[3][2] = { 1,3,5 };
int* p;
p = a[0];
printf("%d", p[0]);//1
return 0;
}
分析:
练习5
int main()
{
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]);
//FFFFFFFC,-4
return 0;
}
练习6
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));//(int*)用来迷惑我的,没必要强制类型转换,类型本来就是对齐的
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10,5
return 0;
}
分析:
练习7(重点)
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);//at
return 0;
}
//
分析:
练习8(重点)
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);//POINT
printf("%s\n", *-- * ++cpp + 3);//ER
printf("%s\n", *cpp[-2] + 3);//ST
printf("%s\n", cpp[-1][-1] + 1);//EW
return 0;
}
分析: