指针的进阶(下)

接上,指针数组,数组存放的是指针 char *arr[5];数组指针,是指针是指向数组的指针

int arr[5];
//arr是一个整形数组,每个元素是int类型的,有5个元素
int* parr1[10];
//parr1是一个数组,数组10个元素,每个元素的类型是int*
int(*parr2)[10];
//parr2是一个指向数组的指针,指向的数组有十个元素,每个元素的类型是int*
int(*parr3[10])[5];
//parr3首先和[]结合,parr3是数组,有十个元素,每个元素的类型int(*)[5],
//parr3是一个存放数组指针的数组

4. 数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1 一维数组传参形参写成数组形式
void test(int arr[10])//或者void test(int arr[]) 形参部分的数组大小可以省略
 形参数组这里是十没有意义数组名传参的时候本质是传的数组首元素地址,形参不需要创建
 一个数组去接受,只要知道了首地址遍历一下就出来了数组后面的内容
 
 
 
形参写成指针形式

void test(int *p)
{

}


void test2(int* arr[])//或者void test2(int *arr[20])数字可以随便写本质上并不会创建一个数组
{

}
void test2(int** p)
{

}
int main()
{
    int arr[10] = { 0 };
    int* arr2[10] = { 0 };
    test1(arr);//数组名表示首元素地址
    test2(arr2);
    return 0;
}

4.2 二维数组传参

int main()
{
    int arr[3][5] = { 0 };
    test(arr);
    return 0;
}

形参写出数组的形式

void test(int arr[3][5])//ok
{}
void test(int arr[][])//err
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算

形参写成指针的形式

传递首元素的地址对于二维数组来说就是第一行的地址

void test(int (*p)[5])

p+1表示来到第二行,p+2表示来到第三行

4.3 一级指针传参

void print(int* p, int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d\n", *(p + i));

      //ptr++//ptr++是让ptr里面的地址++;做为一个整型指针+1就跳过四个字节访问下一个元素
    }
}
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;
}

4.4 二级指针传参

void test(char** ppc)//二级指针接收
{

}
int main()
{
    char a = 'w';
    char* pa = &a;
    char** ppa = &pa;
    test(ppa);//二级指针传递
    return 0;
}

我们要思考的是:当函数的参数为二级指针的时候,可以接收什么参数?

void test(char** ppc)
{

}
int main()
{
    char ch = 'w';
    char* pa = &ch;
    test(&pa);//一级指针取地址
    char** ppa = &pa;
    test(ppa);//二级指针直接传
    char* arr[5];
    test(arr);//传数组的是时候可以直接传指针数组,char*类型,它的地址是char**类型
    return 0;
}

二维数组是不行的,传入的时候

char arr [3][5];

test(arr)//数组名代表首元素地址,对于二级指针来说是第一行,每个元素的类型是char(*p)[5]

void test01(int (*p)[5])
{}
void test02(int (*p)[3][5])
{}
int main()
{
    int arr[3][5];
    test1(arr);//传递的是行的地址
    test2(&arr);//传递是整个二维数组的地址--不常见
    return 0;
}

对二维数组传递过去对二维数组解引用,代表的是第一行的地址

5. 函数指针

数组指针是指向数组的指针,自然函数指针是指向函数的指针

int add(int x, int y)
{
    return x + y;
}
int main()
{
    int arr[10];
    int(*p)[10] = &arr;//p是一个数组指针变量
    printf("%p ", &add);
    printf("%p ", add);
    //尽管写法不一样但是意义相同,都表示函数的地址。不像数组有首元素地址跟数组地址之分。
    int (*pf)(int, int) = add;//这里的pf就是函数指针变量
    //pf先跟*结合证明是指针,之后有两个int参数指向add,去除掉pf就是函数指针类型
    int ret=(*pf)(2, 3);//这里的*可有可无,无论写几个*都可以的。但是有*符合语法规则
    //加*之后必须加()要不就是对函数的结果进行解引用
    printf("%d\n", ret);
    //通过指针可以找到函数,add(2,3)也能正常调用
    //由函数指针可以看出,add赋给了pf,pf也就是add
    int ret1 = pf(2, 3);
    printf("%d\n", ret1);
    return 0;
}
int main()
{
    //void (*) ()是函数指针类型
    //( void (*p)() ) 类型放在括号里是强制类型转换的意思
    //这里就是对0进行强制类型转换成函数指针,我们认为0地址处放置这么一个函数
    1.(*( void (*)() ) 0 )();
    //1.这个代码的意思是首先把0强制类型转换为一个函数指针类型,这就意味着0地址处放一个
    //返回类型是void,无参的一个函数
    //2.调用0地址处的这个函数


    2.void (*signal(int, void(*)(int)))(int);//函数声明
    //这个函数同样可以简化
    // typedef void (*pf_t)(int); 给函数指针类型void(*)(int)重新起名叫:pf_t;
    // 所以上面的函数 简化为:
    // pf_t singal(int,pf_t);
     
    
    //signal是一个函数的声明
    //signal函数的参数,第一个是int类型的,第二个是void(*)(int)的函数指针类型
    //signal函数的返回值类型也是:void(*)(int)的函数指针
    return 0;
}

6. 函数指针数组

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()
{
    //指针数组
    //字符指针数组
    char* arr[5];
    //整型指针数组
    int* arr1[5];
    //函数指针数组
    int(*pf1)(int, int) = add;
    int(*pf2)(int, int) = sub;
    int(*pf3)(int, int) = mul;
    int(*pf4)(int, int) = div;

    //函数指针数组,是数组先跟[]结合
    int(*pf[4])(int, int) = { add,sub,mul,div };
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        int ret = pf[i](8, 2);
        printf("%d ", ret);
    }
    return 0;
}

函数指针数组的用途:转移表

实例化一个计算器

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;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf("*************************\n");
        printf(" 1:add           2:sub \n");
        printf(" 3:mul           4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = add(x, y);
            printf("ret = %d\n", ret);
            break;
        case 2:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = sub(x, y);
            printf("ret = %d\n", ret);
            break;
        case 3:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = mul(x, y);
            printf("ret = %d\n", ret);
            break;
        case 4:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = div(x, y);
            printf("ret = %d\n", ret);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);

    return 0;
}

这里的代码冗余。

利用函数指针来优化我们的代码

#include <stdio.h>
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;
}
int main()
{
    
        int x, y;
    int input = 1;
    int ret = 0;
    int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
    while (input)
    {
        printf("*************************\n");
        printf(" 1:add           2:sub \n");
        printf(" 3:mul           4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        if ((input <= 4 && input >= 1))
        {
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = (*p[input])(x, y);
        }
        else
            printf("输入有误\n");
        printf("ret = %d\n", ret);
    }
    return 0;
}


7. 指向函数指针数组的指针

int add(int x, int y)
{
    return x + y;
}
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int(*p)[10] = &arr;//数组指针
    int* arr2[10];
    int(*p1)[10] = &arr2;
    //函数指针
    int (*pf)(int, int) = &add;
    //函数指针数组
    int (*pfarr[5])(int, int);//存放函数的指针
    int(*(*p3)[5])(int ,int)=&pfarr;//p3指向是一个函数指针数组的指针
    return 0;
}
//*p3==pfarr
//(*p3)[i]==pfarr[i]

8. 回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应,简单来说有AB两个函数,把A的地址作为参数传给B函数,B函数中有参数去接收它,在B函数里面通过指针去访问A函数叫做回调函数。

利用函数回调可以优化我们的代码,就拿上面的计算器实例来说

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 cal(int (*p)(int, int))
{
    int x = 0;
    int y = 0;
    printf("请输入两个操作数\n");
    scanf("%d %d", &x, &y);
    int ret = p(x, y);
    printf("%d\n", ret);
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf("*************************\n");
        printf(" 1:add           2:sub \n");
        printf(" 3:mul           4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            cal(&add);
            break;
        case 2:
            cal(&sub);
            printf("ret = %d\n", ret);
            break;
        case 3:
            cal(&mul);
            break;
        case 4:
            cal(&div);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);

    return 0;
}

//qsort是一个库函数
//基于快速排序算法实现的一个排序函数
//可以排任意类型的数据

//冒泡排序也是一种排序,但是不能排所有类型的数据,简单回顾一下,不具有通用性
 

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 - i - 1; j++)//一趟两两排序的对数
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	} 
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 2,1,3,5,6,4,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	return 0;
}

 

 

这里我们知道qsort的头文件是stdlib.h 

#include<stdlib.h>
#include<string.h>
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 - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	} 
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

void test1()
{
	int arr[10]= { 2,1,3,5,6,4,7,8,9,10 };
	//排序为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
}
int cmp_int(const void* e1, const void* e2)
{
	//这里比较不能用*e1跟*e2直接比较
	//e1和e2都是void *指针,它是无确切型指针,是不能直接解引用的,用来接收任意类型。
	//可以强制转换成你所需要的类型
	/*if (*(int*)e1 > *(int*)e2)
	{
		return 1;
	}
	else if (*(int*)e1 == *(int*)e2)
	{
		return 0;
	}
	else
	{
		return -1;
	}*/
	//这里的返回类型太麻烦了,可以有下面的优化
	return (*(int*)e1 - *(int*)e2);
}
void print_arr(int arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void test2()
{
	int arr[10] = { 2,1,3,5,6,4,7,8,9,10 };
	//排序为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	//比较函数要求qsort函数的使用者自定义一个比较函数
	//排序的整型数据:用> <
	//排序的结构体数据:可能不方便直接使用>  <比较了
	//使用者根据实际情况,提供一个函数,实现2个数据的比较
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}
//使用qsotr排序结构体
struct stu
{
	char name[20];
	int age;
	double score;
};
int com_stu_by_age(const void *e1,const void *e2)//按照年龄排
{

	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
int com_stu_by_name(const void* e1, const void* e2)//比较名字的时候,字符串不能相减用strcmp
{

	return strcmp(((struct stu*)e1)->age , ((struct stu*)e2)->age);
}

void test3()
{
	struct stu arr[3] = { {"张三",20,55.5},{"李四",30,88.0},{"王五",50,90.0}};
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz1, sizeof(arr[0]), com_stu_by_age);
	qsort(arr, sz1, sizeof(arr[0]), com_stu_by_name);
}
int main()
{
	//test2();
	test3();
	return 0;
}
//升序排用e1-e2,降序排用e2-e1;

strcmp库函数也给规定好了

 abcdef跟abq比较;前面都相同,c的ASCII值一定是小于q就返回<0

qsort中自己设计的cmp函数,如果传入参数e1指向的对象比e2大,返回一个大于0的数字

 图中是正常情况

如果e1<e2返回大于0的数字,逻辑与之相反

如果跟库函数中的逻辑相同,排出来就是升序

相反排出来就是降序。

了解了qsort之后我们可以将我们的冒泡排序也改造成不只是排整型数据。

int cmp_int(const void* e1, const void* e2)
{
	
	return (*(int*)e1 - *(int*)e2);
}
//1和2在内存中存储(小端) 01 00 00 00   02 00 00 00
//buf1指向01,buf2指向02,char*每次只能操作一个字节,先交换01和02,其他的00跟00可以之后交换
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_sort(void *base,int num,int width,int (*cmp)(const void *e1,const void *e2))

{
	int i = 0;
	for (i = 0; i < num - 1; i++)//趟数
	{
		int j = 0;
		for (j = 0; j < num - i - 1; j++)
		{
			//if (arr[j] > arr[j + 1])
			if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
			//这里怎么用cmp函数表达之前数组比较大小的效果
		    //我们拿数组举例子
			//int arr[5]={1,2,3,4,5);
			//想取1和2进行比较,函数参数只给我们提供了void *base
			//怎么找出1和2,base是void*指针,用char*强制转换成char*类型的指针
			//占四个字节,(char*)base+width,整型的话这个width就是4
		    //一个char*指针加1就加一个字节,加4就加四个字节可以找到2.
			//给width*j就可以找到想找到的元素的地址
			{
				//交换
				swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
	
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void test4()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}
int main()
{
	test4();
	return 0;
}

 这时候我们的冒泡可以排任何类型的数据。包括之前的结构体

下面,就是一些不同形式的应用

//数组名是什么?
//数组名通常来说是数组首元素的地址
//但是有两个例外
//1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组大小
//2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//4*4=16
	printf("%d\n", sizeof(a + 0));//这里不是单独的数组名,表示的首元素地址,整型的地址+0,//4/8(64位)
	printf("%d\n", sizeof(*a));//a表示首元素地址,*a表示数组的第一个元素,sizeof(*a)是第一个元素的大小,4
	printf("%d\n", sizeof(a + 1));//a表示数组首元素地址,a+1数组第二个元素的地址,sizeof(a+1)就是第二个元素地址的大小,4/8
	printf("%d\n", sizeof(a[1]));//数组的第二个元素,4
	printf("%d\n", sizeof(&a));//这块是数组的地址,但是数组的地址也是地址,大小就是四个或者八个字节。
	printf("%d\n", sizeof(*&a));//&a取整个数组,*a拿到整个数组,也就是整个数组的大小,16,相当于抵消了,sizeof(a)
	printf("%d\n", sizeof(&a + 1));//&a是数组地址,+1跳过整个数组,产生的是4后面的地址,但是还是地址,就是4/8个字节
	printf("%d\n", sizeof(&a[0]));//取数组的第一个元素的地址,4/8
	printf("%d\n", sizeof(&a[0] + 1));//取数组第二个元素的地址 4/8
	return 0;
}
#include<string.h>
int main()
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//6byte,如果是strlen的话这里没有\0,会出现随机值
	printf("%d\n", sizeof(arr + 0));//4/8,arr+0数组首元素地址
	printf("%d\n", sizeof(*arr));//*arr是首元素,大小是一个字节
	printf("%d\n", sizeof(arr[1]));//arr[2]是数组的第二个元素,大小是一个字节
	printf("%d\n", sizeof(&arr));//4/8 &arr是数组的地址,被放在char(*p)[6]数组指针当中
	printf("%d\n", sizeof(&arr + 1));//&arr+1是从数组地址开始向后跳过了整个数组产生的一个地址,是地址就是4/8字节
	printf("%d\n", sizeof(&arr[0] + 1));//取出首元素地址+1,是数组第二个元素的地址,是4/8个字节

	//strlen函数默认内部传入是(const char *string)
	printf("%d\n", strlen(arr));//随机值,这里arr没有上述两种情况,arr就是首元素地址,找\0,f之后的\0是未知的
	printf("%d\n", strlen(arr + 0));//随机值
	printf("%d\n", strlen(*arr));//arr是首元素地址,*arr是数组首元素,'a'-97,strlen就是给它一个地址从内里开始数直到遇见\0,这里给了97从这个地方开始数,97这里被当作一个地址返回给strlen这里可能会出现野指针的问题,是一个错误代码
	printf("%d\n", strlen(arr[1]));//arr[1]-b-98,把98也给了strlen也是野指针了,错误代码
	printf("%d\n", strlen(&arr));//首元素地址跟数组地址都在首元素地址内,所以返回的还是随机值
	printf("%d\n", strlen(&arr + 1));//跟上面原理相同,也是随机值只不过是从数组尾开始数,跟上面的始终差6
	printf("%d\n", strlen(&arr[0] + 1));//取的是‘a’的地址a+1是b,从b开始数,也是随机值

	return 0;
}
#include<string.h>
int main()
{
	char arr[] = "abcdef";//a b c d e r \0
	printf("%d\n", sizeof(arr));//7
	printf("%d\n", sizeof(arr + 0));//arr是数组名,没有单独放在sizeof里面就是首元素地址,也就是a的地址,a+0数组首元素地址 4/8
	printf("%d\n", sizeof(*arr));//*arr数组首元素。1
	printf("%d\n", sizeof(arr[1]));//1数组的第二个元素
	printf("%d\n", sizeof(&arr));//是数组的地址 4/8
	printf("%d\n", sizeof(&arr + 1));//指到了\0后面的地址,是4/8个字节
	printf("%d\n", sizeof(&arr[0] + 1));//数组第二个元素的地址,是4/8byte

	printf("%d\n", strlen(arr));//6,strlen统计的是\0之前有几个字符
	printf("%d\n", strlen(arr + 0));//6
	printf("%d\n", strlen(*arr));//err,97当地址给strlen,非法访问
	printf("%d\n", strlen(arr[1]));98给了strlen,也是非法访问
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//\0后面的地址开始数。是随机值
	printf("%d\n", strlen(&arr[0] + 1));//5

	//sizeof 计算的是对象所占内存的大小-单位是字节
	//不在乎内存放是什么,只在乎内存大小
	//sizeof是一个单目操作符,跟加减乘除一样是不能被模拟的

	//strlen是库函数
	//求字符串长度,从给定的地址向后访问字符,统计\0之前出现的字符的个数
	//可以被模拟实现
	return 0;
}
int main()
{
	char* p = "abcdef";//p存的是a的首元素地址
	printf("%d\n", sizeof(p));//4/8,p是首元素地址,这里求的是地址(指针)的大小
	printf("%d\n", sizeof(p + 1));//char*的指针+1,b的地址,还是求指针大小,4/8
	printf("%d\n", sizeof(*p));//*p就是a,1
	printf("%d\n", sizeof(p[0]));//1 p[0]->*(p+0)->*p
	printf("%d\n", sizeof(&p));//取的是指针变量的地址,也是地址4/8
	printf("%d\n", sizeof(&p + 1));//&p+1是跳过p之后的地址,char**q=&p;
	printf("%d\n", sizeof(&p[0] + 1));//b的地址,4/8个byte


	printf("%d\n", strlen(p));//p存的是a的地址,把a的地址给strlen,从a往后数6个
	printf("%d\n", strlen(p + 1));//b的地址,从b开始数是5个
	printf("%d\n", strlen(*p));//err,返回的是97,非法操作,野指针
	printf("%d\n", strlen(p[0]));//err,返回的是97,非法操作,野指针
	printf("%d\n", strlen(&p));//这是p的地址往后数,数到\0,是未知的,应该是随机值
	printf("%d\n", strlen(&p + 1));//取地址+1,向后访问,也跟上面相同是随机值
	printf("%d\n", strlen(&p[0] + 1));//a的地址+1是b的地址,数到\0是5
	return 0;
}
int main()
{
	//二维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//数组名单独放在sizeof(arr)计算的是整个数组的大小,字节是3*4*4=48
	printf("%d\n", sizeof(a[0][0]));//第一行第一个元素的大小,是4
	printf("%d\n", sizeof(a[0]));//第一行的数组名单独放在sizeof内计算的就是第一行的大小,是16
	printf("%d\n", sizeof(a[0] + 1));//a[0]没单独放在sizeof内部,前面也没有直接&,就说明是第一行第一个元素的地址,+1是第一行第二个地址,是地址就是4/8个字节
	printf("%d\n", sizeof(*(a[0] + 1)));//*(a[0]+1)表示的是第一行第二个元素,就是4
	printf("%d\n", sizeof(a + 1));//a表示首元素地址,a是二维数组也就是第一行的地址,所以a+1表示第二行的地址,也是4/8个字节
	printf("%d\n", sizeof(*(a + 1)));//这是对第二行的地址解引用,就是第二行答案就是16
	printf("%d\n", sizeof(&a[0] + 1));//a[0]是第一行的数组名,&a[0]取出来就是第一行的地址,第一行的地址加1就是第二行的地址,也是4/8个字节
	printf("%d\n", sizeof(*(&a[0] + 1)));//对第二行的地址解引用就访问的第二行,是16
	printf("%d\n", sizeof(*a));//a是首元素地址,就是第一行地址解引用就是第一行,sizeof*a就是16,这里也可以这样理解*a->*(a+0)->a[0]第一行的大小
	printf("%d\n", sizeof(a[3]));//这里表示的是数组第四行,但是数组没有第四行,这里并没有错还是会打印出来
	//例如:int a=10;
	//     printf("%d\n",sizeof(a));-----4byte
	//     printf("%d\n",sizeof(int));---4byte
	//这里确认多少字节的时候并不会真的去内存看a的大小,而是在创建的时候就知道它是一个int类型
	//所以上面sizeof(a[3])压根就不会去访问数组第四行,而是直接就知道它是一个int[4]是16
	return 0;
	//二维数组的理解:1.可以把二维数组想象成一维数组,二维数组的每一行是一个一维数组。
}

总之,任何数组arr[i]都只是形式上这么去写,为了方便初学者理解,底层本质上是*(arr+i)

下面是一些实际的题:

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int* ptr = (int*)(&a + 1);
    printf("%d,%d", *(a + 1), *(ptr - 1));
    return 0;
}
//程序的结果是什么?
//先进行分析:
//&a(&和a)结合表示是数组的地址,数组取地址放入的是数组指针是int(*p)[5]
//要想跟int *ptr(int类型)保持一致,就用int*强制转换,变成int*指针,这时ptr就指向数组末尾也就是5的后面
//ptr-1就表示向前挪了一个位置,就指向了5现在的解引用就访问整型也就是5
//数组名表示首元素地址,a+1就表示第二个元素,解引用就访问的是2

 

//占20个byte
struct Test
{
	int Num;//4
	char* pcName;//4
	short sDate;//2
	char cha[2];//2
	short sBa[4];//8
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
	p = (struct Test*)0x100000;//虽然是个16进制但是它是拿整数来写的,所以将它强制类型转换,把整型类型变成指针类型
	printf("%p\n", p + 0x1);//结构体指针+1加的是sizeof(这个结构体的大小)转换成16进制就是0x100014
	printf("%p\n", (unsigned long)p + 0x1);//p被转化为一个整数,整数+1就是加1,
	printf("%p\n", (unsigned int*)p + 0x1);//被转换成一个指针就是4,
	//指针类型决定了指针+1到底加几个字节
	return 0;
}

 

int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int* ptr1 = (int*)(&a + 1);
    int* ptr2 = (int*)((int)a + 1);
    printf("%p,%x", ptr1[-1], *ptr2);
    return 0;
}//%x是以16进制形式打印,%o是8进制打印,%p打印地址前面有0不会丢掉要打印够32位也就是4个byte,%x打印只打印有效位
//*a->*(a+0)->a[0],ptr[-1]==*(ptr-1),就是4,
//vs是小端存储模式,四个数分别是这样存储的  01 00 00 00| 02 00 00 00| 03 00 00 00| 04 00 00 00
//                                          低                                          高
//a是数组名表示首元素地址也就是1,指针指向01
//加1是数字1,就指向了01后面的00,之后int*指针访问
//一次访问四个字节就是00 00 00 02,

如果是%#x打印会自动带上0x

 

 

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int* p;
    p = a[0];
    printf("%d", p[0]);
    return 0;
}
//这是一个逗号表达式,不是正常的二维数组赋值,正常的二维数组赋值数字也有{}括起来
//这里是(,),(,),(,)相当于有5个逗号表达式,逗号表达式只表示最右边的,所以数组初始化了1,3,5
//数组存的是
//1   3
//5   0
//0   0
//a[0]既没有跟sizeof单独结合也没有跟&结合,就表示首元素的第一个值是1,地址存入p[0]
//p[0]==*(p+0)==1
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]);
    return 0;
}
// 0 0 0 0 0 
// 0 0 0 0 0
// 0 0 0 0 0
//  p+4  p作为一个数组指针指向的数组是四个元素每个元素是整型,所以加一每次就跳4个整型,对它解引用就是找从这向后的四个元素再[2]也就是+2就是再内四个元素里从0开始访问第三个数
//   |
// 0 0 0 0 0
//       | 
//      p[4][2]
// 0 0 0 0 0
//   |
// &a[4][2]

//&p[4][2]==*(*(p+4)+2)
//指针减指针得到的就是之间差的元素的个数,两个差了四个整型,数组由着下标有小到大增长变化,就相当于是小地址-大地址
//是-4,-4以%p打印出来,-4在内存上是这么存的 1000 0000 0000 0000 0000 0000 0000 0100(反码存储)
//打印的时候是原码1000 0000 0000 0000 0000 0000 0000 0011(反码)
//                1111 1111 1111 1111 1111 1111 1111 1100(原码)
//                  f    f    f    f    f    f    f   c
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;
}
//1,2,3,4,5
//6,7,8,9,10
//&aa取的是整个二维数组的地址,在首元素地址内里。+1走过一个数组,指向10的后面,ptr1是一个整型指针,减1向前挪动一个整型,就打印10、
//ptr2,aa表示首元素地址,指的是第一行的地址,加1指的是第二行的地址,对第二行地址解引用,相当于拿到的是第二行的数组名*aa==aa[1]表示的是6内块位置,ptr2-1表示是5
int main()
{
	char* a[] = { "work","at","alibaba" };//相当于字符串首字符的地址存到了a里面('w'),('a')存到了第二个元素里面,('a')存到了数组第三个元素里,
	char** pa = a;//pa存的a也就是w,pa++打印的就是存a的地址也就是打印的at
	pa++;
	printf("%s\n", *pa);
	return 0;
}

打印字符串的时候给个地址就直接开始从存的首字符开始打印。前提是%s打印

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };//指针数组
	char** cp[] = { c + 3,c + 2,c + 1,c };//first point new enter
	char*** cpp = cp;
	printf("%s\n", **++cpp);//point
	printf("%s\n", *-- * ++cpp + 3);//操作符优先级:先是++cpp,之后*之后--之后会*找到enter,+3找到enter的e,%s打印的er
	printf("%s\n", *cpp[-2] + 3);//因为cpp[-2]==*(cpp-2),所以这个表达式可以转换为**(cpp-2)+3,**(cpp-2)就指向了之前的c+3,再+3找到的就是first的st
	printf("%s\n", cpp[-1][-1] + 1);//==*(*(cpp-1)-1)+1;打印的ew
	return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-Taco-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值