C语言——指针的进阶

目录

字符指针

指针数组

数组指针

数组指针的定义

数组名和&数组名

数组指针的使用 

数组传参和指针传参

函数指针

函数指针数组

指向函数指针数组的指针

回调函数

指针和数组练习题的解析


在前面的文章中我们已经叙述了指针初阶内容:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。

本文我们继续来讨论指针的进阶知识。

字符指针

在指针的类型中我们知道有一种指针的类型为字符指针char*,

下面就是一个小例子:

int main(void)
{
	char* p = "abcdef";//常量字符串
	const char* p = "abcdef";
	//*p = 'w';//err
	printf("%s", p);//
	return 0;
}

int a = 10;//10 是 a 的值属性;int 是 a 的类型属性

 我们把字符串的首地址即 'a' 的地址赋给 *p 这样我们就可以通过指针 p 来找到到该字符串。但是我们如果把 'w' 赋给 *p 就会报错。在这里我的理解是这样的 *p 接受的是一个地址而 'w' 的ASCII码值是119,这样就会把119作为一个地址赋给 *p,这样的行为是危险的。避免这种问题,就可以在前面加上const。

还有一个简单地例子:

int main()
{
	char str1[] = "hello";
	char str2[] = "hello";
	const char *str3 = "hello";
	const char *str4 = "hello";
	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;
}

 通过上述的代码,可以知道常量字符串共有一个地址,而str3与str4创建的是两个拥有单独空间的数组。

指针数组

所谓的指针数组,就是用来存放指针变量的数组。

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

在前面的文章中,我们也介绍过使用指针数组可以构成一个二维数组。

数组指针

数组指针的定义

整型指针 - 指向整型的指针 - int* a; - 存放的是整形的地址

字符指针 - 指向字符的指针 - char* b; - 存放的是字符的地址

数组指针 - 指向数组的指针 - int (*p)[10] ; - 存放的是数组的地址

//int (*p1)[10]; - 数组指针用来指向数组的地址。

//int* p2[10]; - 指针数组该数组有10个元素,每个元素是int*类型。

解释:p1先和*结合,说明p1是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p1是一个指针,指向一个数组,叫数组指针。
//这里要注意:[ ]的优先级要高于*号的,所以必须加上()来保证p先和*结合

数组名和&数组名

int main()
{
    int arr[10] = { 0 };
    printf("arr = %p\n", arr);//首元素地址
    printf("&arr= %p\n", &arr);//整个数组的地址,但是数值是等于上面的值
    printf("&arr[0]= %p\n", &arr[0]);//首元素地址
    printf("&arr[0]= %p\n", &arr[0]+1);//首元素地址 + 1
    printf("arr+1 = %p\n", arr+1);//数组第二个元素的地址
    printf("&arr+1= %p\n", &arr+1);//数组首元素的地址 + 数组所占的字节数后的地址;跳过了整个数组的所占的内存。
    return 0;
}

 数组名通常表示首元素的地址,但是有两个特殊情况:

        1.sizeof(数组名),这里表述的是整个数组,计算的还是整个数组的大小。

        2.&数组名,这里表示的也是整个数组,因此&取出的是整个数组的地址。

数组指针的使用 

int arr[10] = { 0 };
int *p1 = arr;//数组首元素的地址,对于p1来说它的类型为int*
int (*p2)[10] = &arr;//数组的地址,对于p2来说它的类型是 int(*)[10]


//同理若是把下面的指针数组的地址存放入数组指针中
char* arr[5] = { 0 };
char* (*p)[5]  = &arr;
char* (*p)[]  = &arr;//err 在arr的角度,拥有10个元素,站在*p的角度则是0

举一个例子:

int main(void)
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *((*p)+i));//通过(*p)获得数组的地址即首元素的地址,然后递增再将递增的地址进行解引用获得数组中每个数的值
	}
	return 0;
}
//但是我们一般很少这样写代码

下面在介绍一段代码:

void Print2(int(*p)[5], int r, int c)
{
	for (int  i = 0; i < r; i++)
	{
		for (int j = 0; j < c; j++)
		{
			//printf("%d ", *((*(p + i)) + j));//两种代码的输出结果是相同的
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
}

void print1(int arr[3][5], int row, int col);

int main(void)
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
    Print1(arr, 3, 5);	
    Print2(arr, 3, 5);//传入的是首元素的地址,即一维数组的地址
	return 0;
}

一般来说打印二维数组我们可以将数组的作为形参传入进函数,但是有了数组指针后,我们就可以传入一个二位数组的首地址作为形参。

//我们来简单地看一下下面的代码的意思
int arr[5];//整形数组
int *parr1[10];//整型指针的数组
int (*parr2)[10];//数组指针
int (*parr3[10])[5];//数组指针组成的数组 - 这个数组10个元素每个元素是一个数组指针
//int (*parr3[10])[5]; -> parr3[10]中数据存放的类型为int(*)[5] 

数组传参和指针传参

在编写代码的时候,不可避免的要传入数组或是指针,下面来看一下传参的具体信息:

//一维数组传参
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok
{}
void test2(int *arr[20])//ok
{}
void test2(int **arr)//ok - 在数组arr2中存放的数据类型是int*可以使用二级指针
{}
int main()
{
	int arr[10] = { 0 };
	int *arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}
//二维数组传参
void test(int arr[3][5])//ok
{}
void test(int arr[][])//err
{}
void test(int arr[][5])//ok
{}
void test(int *arr)//对于二位数组来说传入的首地址是一维数组的地址,因此int*不能接收
{}
void test(int* arr[5])//err
{}
void test(int(*arr)[5])//ok
{}
void test(int **arr)//err
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
//一维指针传参
void print(int *p, int sz)
{}
int a = 10;
int* ptr = &a;
int arr[10];
//可以传入
print(&a);
print(ptr);
print(arr);

//二维指针传参
void print(int **p, int sz)
{}
int a = 10;
int* ptr = &a;
int* *pptr = &ptr;
int* arr[10];
//可以传入
print(pptr);
print(&ptr);
print(arr);

函数指针

函数指针同理就是指向函数的指针,其中存储的就是函数的地址。

函数指针的类型是 int(*)() 

int Add(int x, int y)
{
	printf("%d\n", x + y);
	return x + y;
}
int main(void)
{
	int(*pf)(int, int) = &Add;
	printf("%p\n", Add);//00771389
	printf("%p\n", &Add);//00771389
	printf("%p\n", *pf);//00771389
	printf("%p\n", pf);//00771389
	
	Add(1, 2);//3
	pf(1, 2);//3
	(*Add)(1, 2);//3
	(*pf)(1, 2);//3
	(****pf)(1,2);//3
    //是否有*对于函数以及函数指针的调用没有影响    
	return 0;
}

再来看两段有趣的代码:

//代码1
(*(void (*)())0)();
//上面的代码可以看做是一个函数的调用

//void (*)() - 空类型的函数指针
//(void (*)())0 - 将0地址强制转换为上述的指针
//*(void (*)()0) - 解引用该指针,寻找到0地址存储的相关函数
//(*(void (*)())0)() - 调用

//关键在与0


//代码2
void (*signal(int , void(*)(int)))(int);
//上面的代码可以看做一个函数的声明

//singal先与()结合
//整体式子中只有数据类型没有变量,可以判断不是函数调用
//*号与函数名没有结合,可以判断不是指针
//整体类似于int signal(int, int);
//声明的signal函数的第一个参数的类型是int,第二个参数的类型是函数指针,该函数指针指向的函数参数是int,返回类型是void;singal函数的返回类型也是一个函数指针该函数指针指向的函数参数是int,返回类型是void。

//上述代码可以视为:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

一个简单地计算器例子来解释说明函数指针的用法:

#include 
void menu()
{
	printf("*****************************\n");
	printf("************1.Add************\n");
	printf("************2.Sub************\n");
	printf("************3.Mul************\n");
	printf("************4.Div************\n");
	printf("************0.Exit***********\n");
}

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 calc(int (*pf)(int,int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个数:>");
	scanf("%d%d",&x, &y);
	printf("结果为%d\n", pf(x, y));
}
int main(void)
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		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:
			break;
		default:
			printf("输入错误");
			break;
		}
	} while (input);
	return 0;
}

在上面的代码实例中,我们使用了一个calc函数接收作为参数的函数指针,这样可以简化重复输入的代码。更加灵活的设计及计算器的功能。 

函数指针数组

与上文叙述的相似,函数指针数组就是存储函数指针的数组

函数指针的类型是:int(*)()

函数指针数组的类型是:int(*p[ ])() - p先与[ ]结合说明是数组,数组地内容就是int(*)()

 以上面的计算器的例子进行修改:

//对主函数进行修改,下面的代码只保留了函数指针数组的运用
int main(void)
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[10])(int, int) = { 0,Add,Sub,Mul,Div };//转移表
    if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
		}
	return 0;
}

 于函数指针类似,使用函数指针数组也可以便捷的在原有的基础上添加我们需要的功能。

指向函数指针数组的指针

指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针

这里就简单地举个例子,不进行仔细的说明

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;
    return 0;
}

回调函数

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

下面我们就来介绍以库函数相关的运用快速排序的思想编写函数qsort为例子,简要的介绍一下回调函数:

void qsort(void* base, size_t num, size_t size, int(*cmp)(const void*, const void*));

void* base - 传入数组的首地址

size_t num - 待排序的数组内的元素数量

size_t size - 待排序的数组内单个元素所占内存的大小(byte)

int(*cmp)(const void*, const void*) - 函数指针 - 比较的方式

#include 
#include 
int cmp(const void* e1, const void* e2)//void*类的指针不能直接解引用,也不能+-整数,但是可以接收任意类型的指针
{
	return *((int*)e1) - *((int*)e2);
}
int main(void)
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp);//qsort函数就回调使用了我们自己编写的cmp函数,但是我们自己的程序里并没有使用
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 真实因为qsort函数有着int(*cmp)(const void*, const void*)这样的函数指针,因此我们可以通过过该指针对我们想要排序的数据类型进行排序,例如:我们可以将结构体按照一定的顺序进行排序,下面通过简单地代码来展现一下:

int cmp02_name(const void* e1, const void* e2)
{
	//Returns an integral value indicating the relationship between the strings:
	return strcmp(((struct Stu*)e1)->Name, ((struct Stu*)e2)->Name);//转换类型是临时的,需要加上()
}
int cmp02_age(const void* e1, const void* e2)
{
	//Returns an integral value indicating the relationship between the strings:
	return strcmp(((struct Stu*)e1)->age, ((struct Stu*)e2)->age);//转换类型是临时的,需要加上()
}
void test02_age()
{
	struct Stu s[3] = { {"zhangsan",15 }, {"lisi", 25},{"wangwu", 30} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp02_age);
}
void test02_name()
{
	struct Stu s[3] = { {"zhangsan",15 }, {"lisi", 25},{"wangwu", 30} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp02_name);
}
int main(void)
{	
	test02_name();
    test02_age();
	return 0;
}

我们可以通过前几次文章叙述的调试方法对其进行调试,就可以看到在内存中,数据顺序已经发生了变化。

//对冒泡排序法按照qsort的形参进行修改
//冒泡排序源代码
void bubble_sort(int arr[] , int sz)
{
	int flag = 1;
	for (int  i = 0; i < sz-1; i++)
	{
		for (int j = 0; j < sz-1-i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}
void test01()//使用冒泡排序进行的测试
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

//改进后的冒牌排序法
//自己写的交换函数
//void Swap(const void* e1, const void* e2,int width)
//{
//	for (int i = 0; i < width; i++)
//	{
//		char tmp = *(char*)e1;
//		*(char*)e1 = *(char*)e2;
//		*(char*)e2 = tmp;
//		((char*)e1)++;
//		((char*)e2)++;
//	}
//}

//比较好的交换函数
void Swap(char* buf1, char* buf2, int width)
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void bubble_sort_qsort(void* base, int sz, int width, int (*cmp)(const void* e1,const void* e2))
{
	int flag = 1;
	for (int  i = 0; i < sz-1; i++)
	{
		for (int 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);
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}

}

void test02()//使用改进后的函数进行排序
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort_qsort(arr, sz, sizeof(arr[0]), cmp);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main(void)
{	
	//test01();
	//test02();
	return 0;
}

 经过测试,上述对冒泡排序进行修改的代码也可以对结构体类型的数据进行排序,由此可见我们的修改是成功的。

指针和数组练习题的解析

下面我们来分析一些指针与数组的练习题 :

strlen是字符串长度的,关注的是字符串中的'\0',计算的是'\0'之前出现的字符个数

strlen是库函数,只针对字符串

sizeof只关注占用内存空间的大小不在乎内存中放的是什么

sizeof是操作符

int main(void)
{
	//一维数组
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));// 16
	//a表示表示整个数组的大小
	printf("%d\n", sizeof(a + 0));// 4/8
    //a不是单独在sizeof内部,也没有取地址,所以这里是首元素的地址
	//数组的首元素地址
	printf("%d\n", sizeof(*a));// 4
	//数组首元素的大小
	printf("%d\n", sizeof(a + 1));// 4/8
	//数组第二个元素的地址
	printf("%d\n", sizeof(a[1]));// 4
	//数组第一个元素所占内现的大小
	printf("%d\n", sizeof(&a));// 4/8
    //取出的是数组的地址
	printf("%d\n", sizeof(*&a));// 16
    //&a -> int(*)[4], 对数组指针的类型做解引用
	//对整个数组的地址解引用得到整个数组的大小
	//*和&抵消
	printf("%d\n", sizeof(&a + 1));// 4/8
    //&a -> int(*)[4];&a+1 - 从数组a的地址向后跳过了4个整型元素的数组
	//数组首地址跳过整个数组大小的内存后的地址
	printf("%d\n", sizeof(&a[0]));// 4/8
	//数组首元素的地址
	printf("%d\n", sizeof(&a[0] + 1));// 4/8
	//数组第二个元素的地址
	return 0;
}
int main(void)
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));// 6
	//数组所占空间的大小
	printf("%d\n", sizeof(arr + 0));// 4/8
	//数组首元素的地址
	printf("%d\n", sizeof(*arr));// 1
	//数组首元素所占空间的大小
    //*arr -> arr[0];arr + 0 -> arr[0]
	printf("%d\n", sizeof(arr[1]));// 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
    //sizeof(arr[0] + 1) = 4 -> arr[0]是字符型数据,而1是整型数据,因此sizeof里面会发生整型提升
	//数组第二个元素的地址


    printf("%d\n", strlen(arr));// 随机值1
    //未识别到'\0'
    printf("%d\n", strlen(arr + 0));// 随机值1
    //printf("%d\n", strlen(*arr));// 数组首元素即'a',报错 -> strlen('a') -> strlen(97) -> 野指针
    //printf("%d\n", strlen(arr[1]));// 报错
    printf("%d\n", strlen(&arr));// 随机值1
    printf("%d\n", strlen(&arr + 1));// 随机值1 - 6
    printf("%d\n", strlen(&arr[0] + 1));// 随机值1 - 1
    return 0;
}
int main(void)
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));// 7
	//字符串数组包括'\0'
	printf("%d\n", sizeof(arr + 0));// 4/8
	//数组首元素的地址
	printf("%d\n", sizeof(*arr));// 1
	//数组首元素所占空间大小
	printf("%d\n", sizeof(arr[1]));// 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
	//数组第二个元素的地址


	printf("%d\n", strlen(arr));// 6
	//数组的长度不包括'\0'
	printf("%d\n", strlen(arr + 0));// 6
	//数组首元素地址到'\0'的长度
	//printf("%d\n", strlen(*arr));// err
	//printf("%d\n", strlen(arr[1]));// err
	printf("%d\n", strlen(&arr));// 6
	//数组的地址,首元素地址
	printf("%d\n", strlen(&arr + 1));// 随机值
	printf("%d\n", strlen(&arr[0] + 1));// 6 - 1
	return 0;
}
int main(void)
{
	char *p = "abcdef";//常量字符串,指针p是指向字符a的地址
	printf("%d\n", sizeof(p));// 4/8
	//指针所占内存的大小
	printf("%d\n", sizeof(p + 1));// 4/8
	//指针所占内存的大小
	printf("%d\n", sizeof(*p));// 1
	//首元素的类型
    //p[0] = *(p+0) = *p
	printf("%d\n", sizeof(p[0]));// 1
	//首元素的类型
	printf("%d\n", sizeof(&p));// 4/8
	//首元素的地址
	printf("%d\n", sizeof(&p + 1));// 4/8
	//数组首地址跳过整个字符串大小的内存后的地址
	printf("%d\n", sizeof(&p[0] + 1));// 4/8
	//第二个元素的地址


	printf("%d\n", strlen(p));// 6
	printf("%d\n", strlen(p + 1));// 5
	//printf("%d\n", strlen(*p));// err
	//printf("%d\n", strlen(p[0]));// err
	printf("%d\n", strlen(&p));// 随机值1
    //p中存放的是a的地址,p后面什么时候到'\0'未知
	printf("%d\n", strlen(&p + 1));// 随机值2
    //p中存放的地址未知,无法判定p中存放的地址是否有'\0'
	printf("%d\n", strlen(&p[0] + 1));// 5
	return 0;
}

int main(void)
{
	//二维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));// 48
	//数组所占空间大小
	printf("%d\n", sizeof(a[0][0]));// 4
	//数组首元素所占空间大小
	printf("%d\n", sizeof(a[0]));// 16
    //a[0]代表数组第一行一维数组的数组名,一维数组的大小
	//二维数组第一行元素所占空间
	printf("%d\n", sizeof(a[0] + 1));// 4/8
    //a[0]作为第一行的数组名不是单独放在sizeof内部
    //a[0]代表第一行第一列的地址->a[0][0]的地址
	//a[0][1]的地址
	printf("%d\n", sizeof(*(a[0] + 1)));// 4
	//a[0]代表第一行第一列的地址->a[0][0]的地址
	//a[0][1]的地址的解引用
	printf("%d\n", sizeof(a + 1));// 4/8
    //a虽然是二维数组的地址但是没有单独放在sizeof内部,也没有取地址
    //a表示首元素的地址,即第一行的地址
	//数组第二行的地址
	printf("%d\n", sizeof(*(a + 1)));// 16
	//数组第二行元素所占空间大小
    //*(a+1)->a[1]
	printf("%d\n", sizeof(&a[0] + 1));// 4/8
    //&a[0] - 对第一行的数组名取地址,拿出第一行的地址
	//数组第二行元素地址
	printf("%d\n", sizeof(*(&a[0] + 1)));// 16
	//数组第二行元素所占空间
	printf("%d\n", sizeof(*a));// 16
    //a表示首元素的地址,就是第一行的地址
	//数组第一行元素所占空间大小
	printf("%d\n", sizeof(a[3]));// 16
	//sizeof只要知道类型就可以执行,不会去访问第四行
	return 0;
}

 还有几道程序题:

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int *ptr = (int *)(&a + 1);//&a取出的是数组a的地址
	printf("%d,%d", *(a + 1), *(ptr - 1));//2,5
	return 0;
}
struct Test
{
    int Num;
    char *pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p = (struct Test*)0x100000;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
    printf("%p\n", p + 0x1);// 14
    //0x100000 + 20 -> 0x100014
    printf("%p\n", (unsigned long)p + 0x1);// 8 - err
    //0x100000 + 1 -> 0x100001
    printf("%p\n", (unsigned int*)p + 0x1);// 4
    //0x100000 + 4 -> 0x100004
    return 0;
}
int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);
    int *ptr2 = (int *)((int)a + 1);//将a的地址强制转换成int,然后 + 1
    //小段存储: 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
    //得到的地址就是第二个字节的地址开始打印即:00 00 00 02 -> 02 00 00 00
    printf( "%x,%x", ptr1[-1], *ptr2);//0x00000004 0x2000000
    return 0;
}
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    //正常的赋值情况应该为{{0,1},{2,3},{4,5}}但是在这里放的是(),内部使用的是,表达式即:{1,3,5}
    int *p;
    p = a[0];
    printf( "%d", p[0]);
    return 0;
}
int main()
{
    int a[5][5];//a -> int(*)[5];a -> int(*)[4]
    int(*p)[4];//数组指针指向的是数组元素为4的数组
    p = a;
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    //地址由低到高存储
    //-4以%p的形式进行打印时,就是把-4看做地址打印其在内存中的存储,即-4的补码
    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;
}
int main()
{
    char *a[] = {"work","at","alibaba"};
    char**pa = a;
    pa++;
    printf("%s\n", *pa);//at
    return 0;
}
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 cpp - 2 -> *(cpp - 2)
    printf("%s\n", cpp[-1][-1]+1);//EW
    return 0;
}

你可能感兴趣的:(C语言,c语言)