指针的进阶

一、字符指针

一般的使用方式为:1.单个字符

#include <stdio.h>
int main()
{
	char h = 'A';
	char* pa = &h;
	return 0;
}

2.字符串

#include <stdio.h>
int main()
{
	char* pb = "abcdef";
    *pb = 'w';
    printf("%s",pb);
	return 0;
}

 pb里存放的是"abcdef"首字符 'a' 的地址

字符串常量 "abcdef"是存储在内存的只读数据区,不能通过指针对其修改,*pb='w' 表达错误,程序会挂掉。

很多编译器,在char*pb = "abcdef" 这里就会报警告

为了让代码更严谨,char*pb = "abcdef" 应改为 const char*pb = "abcdef" ,把语法限制死,使这种写法一出现就会提醒自己。

 所以正确的代码风格写法:

#include <stdio.h>
int main()
{
	const char* pb = "abcdef";
	printf("%s\n", pb);
	return 0;
}

const修饰指针变量的作用:

1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。

#include <stdio.h>
int main()
{
	int a = 5;
	int b = 4;
	const int* p = &a;
	*p = 4;
	p = &b;
	return 0;
}

----------------------------- *p不可修改,p可修改

2. const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

#include <stdio.h>
int main()
{
	int a = 5;
	int b = 4;
    int* const p = &a;
	*p = 4;
	p = &b;
	return 0;
}

 ------------------------*p可修改,p不可修改

3.*的左右都有const,则指针变量本身和指针变量的内容都不能修改。

#include <stdio.h>
int main()
{
	int a = 5;
	int b = 4;
    const int* const p = &a;
	*p = 4;
	p = &b;
	return 0;
}

 ----------------------*p和p都不能修改

《剑指offer》里有道题:求以下代码的输出结果

#include <stdio.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	const char* arr3 = "abcdef";
	const char* arr4 = "abcdef";
	if (arr1 == arr2)
		printf("arr1 and arr2 are the same\n");
	else
		printf("arr1 and arr2 are not the same\n");

	if(arr3 == arr4)
		printf("arr3 and arr4 are the same\n");
	else
		printf("arr3 and arr4 are not the same\n");
	return 0;
}

arr1和arr2为数组,arr3和arr4为指针。它们的输出结果如下:

1.用相同的常量字符串去初始化 ,不同的数组的时候就会开辟出不同的内存块。arr1和arr2均为字符串首字符 ' a ' 的地址,但是两数组存放常量字符串 "abcdef" 的地址不同,所以arr1和arr2(实为首字符存放地址比较)自然不相等。
2.C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个常量字符串的时候,他们实际会指向同一块内存。这里arr3和arr4均为字符指针,指向的是同一个常量字符串。所以arr3和arr4所指向的首字符地址自然相同。

所以arr1和arr2不相同,arr3和arr4相同。


关键词typedefdefine的不同:

typedef int* pint;
#define PINT int*
#include <stdio.h>
int main()
{
	int* b, c;//相当于int* b,int c
	pint a, b;//相当于int* a,int* b
	PINT d, e;//相当于int *d,int e
	return 0;
}

typedef是创造一个新的类型,如int,char等类型。(如上代码,typedef创造出了一个新的类型pint,但是作用和int*一模一样)

#define是换了另一种表达方式来表达,相当于替换。(如上代码,用 PINT 来表示 int*)


二、指针数组

指针数组是一个存放指针的数组。

#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 5,6,7,8,9 };
	int arr3[] = { 9,10,11,12,13 };
	int* arr[] = { 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]);//arr[1][j]=arr1[j],相当于*(*(arr+1)+j)
		}
		printf("\n");
	}
	return 0;
}

运行结果:


三、数组指针

数组指针:能够指向数组的指针

#include <stdio.h>
int main()
{
	int* arr1[10];
	//数组里面的10个元素都是指针

	int(*arr2)[10];
	//arr2先和*结合,说明arr2是一个指针变量,然后指向的是一个大小为10个整型的数组。所以arr2是一个指针,指向一个数组,叫数组指针。
	//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证arr2先和*结合。
    //指向的数组里面的元素类型为整型
	return 0;
}

数组名和&数组名的区别:

#include <stdio.h>
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	printf("%p\n");
	printf("%p\n");
	return 0;
}

两者打印的地址相同,但是它们并不是一样的,在看一段代码:

#include <stdio.h>
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	printf("%p\n",arr);
	printf("%p\n", arr + 1);
	printf("%p\n",&arr);
	printf("%p\n", &arr + 1);
	return 0;
}

 根据上面打印的结果可以发现:&arrarr,虽然值是一样的,但是意义应该是不一样的。

&arr 表示的是数组的地址,而不是数组首元素的地址。

这段代码中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,元素的地址+1,跳过一个元素大小
所以 &arr+1 相对于 &arr 的差值是40。

数组指针的使用:

#include <stdio.h>
int main()
{
    int arr[5] = { 1,2,3,4,5 };
    int(*p)[5] = &arr;//把数组arr的地址赋值给数组指针变量p
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        printf("%d ", *(*p + i));
    }
    return 0;
}
我们一般很少这样写代码。用在二维数组更多。
#include <stdio.h>
void pint(int(*p)[3],int r,int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
	pint(arr,3,3);
	return 0;
}

一般数组名arr,表示的是首元素地址。但是二维数组的首元素是二维数组的第一行,所以这段代码传递的arr,其实相当于第一行的地址,是整个一维数组的地址,可以用数组指针来接收。

做下对比以区分:

#include <stdio.h>
int main()
{
	int(*arr1)[10];
	//arr1和*结合,说明arr1是一个指针,该指针指向一个数组
	//这个数组有10个元素,每个元素是int型
	//arr1是数组指针
	int(*arr2[10])[5];
	//arr2和[]结合,说明arr2是一个数组,数组里面有10个元素
	//数组的每个元素是一个数组指针,类型是int(*)[5]
	//该类型的指针指向的数组有5个int类型的元素
	return 0;
}

四.数组参数、指针参数

1.一维数组传参

#include <stdio.h>
void test1(int arr[])
{}
void test2(int arr[10])
{}
void test3(int* arr)
{}

void test4(int*arr2[20])
{}
void test5(int**arr2)//用二级指针接收一级指针的地址
{}
int main()
{
	int arr1[3] = { 0 };
	int* arr2[3] = { 0 };//元素均为int*型指针
	test1(arr1);
	test2(arr1);
	test3(arr1);

	test4(arr2);
	test5(arr2);
	return 0;
}

2.二维数组传参

#include <stdio.h>
void test1(int(*arr)[5])
{}
void test2(int**arr)//不可行
{}
void test3(int arr[3][5])
{}
int main()
{
	int arr[3][5] = { 0 };//二维数组名是第一行的地址,类型为int(*)[5]
	test1(arr);//可行
	test2(arr);//不可行
	test3(arr);//可行
	return 0;
}
二维数组传参,函数形参的设计只能省略第一个[ ](即列)的数字。 因为对一个二维数组而言,可以不知道有多少行,但是必须知道一行多少元素。 这样才方便运算。
3.一级指针传参
#include <stdio.h>
void pint(int* p, int len)
{
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", *(p+i));
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;
	int len = sizeof(arr) / sizeof(arr[0]);
	pint(p, len);//一级指针p,传给pint函数
	return 0;
}
4.二级指针传参
#include <stdio.h>
void test1(int** pp)
{
	printf("%d\n", **pp);
}
int main()
{
	int i = 5;
	int* arr[5];//每个元素均为一级指针,类型为int*
	int* p = &i;
	int** pp = &p;
	test1(pp);//效果均相同
	test1(&p);//
	test1(arr);//
	return 0;
}

5.函数指针

#include <stdio.h>
void test()
{}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

 由运行结果可知:这两个均为test函数的地址。

函数指针存放函数地址,如何使用

#include <stdio.h>
void test1()
{}
int test2(int x,int y)
{}
char* test3(char*a,int x)
{}
int main()
{
	void(*p1)() = test1;//函数指针名p1,类型为void(*)()
	int (*p2)(int, int) = test2;//函数指针名p2,类型为int(*)(int,int)
	char* (*p3)(char*, int) = test3;//函数指针名p3,类型为char*(*)(char*,int)
	return 0;
}

如函数指针p2,p3先与*结合,说明p3是指针,指针指向的是一个函数,指向的函数参数为(char*,int),返回值类型为char*。

阅读出自《C陷阱与缺陷》的两段有趣代码:

1.

 1.把0强制转化为void(*)()类型的函数指针

2.再去调用0地址处这个函数,该函数为无参,返回类型为void

2.

第二段代码需要将其拆分来读 

 1.signal是一个函数声明,这个函数有两个参数(绿色框框部分)。第一个参数为int类型;第二个参数为函数指针,该指针指向的函数参数为int,返回类型是void。

2.signal函数的返回类型也是函数指针(紫色框框部分),该指针指向的函数参数为int,返回类型的void。

代码2太复杂,我们可以将其简化:

typedef void(*p)(int);//p为新的类型名
#include <stdio.h>
int main()
{
	p signal(int, p);//与void (*signal(int , void(*)(int)))(int)等同
	return 0;
}

六、函数指针数组

定义:

#include <stdio.h>
int test1(int x)
{}
int test2(int y)
{}
int test3(int z)
{}
int main()
{
	int (*arr[3])(int) = { test1,test2,test3 };
	//arr 先和 [] 结合,说明 arr是数组,数组里的元素均是 int (*)(int) 类型的函数指针。
	return 0;
}

具体用途可以通过一个简易计算机来体现:

#include <stdio.h>
void menu()
{
	printf("*****     1.add     *********\n");
	printf("*****     2.sub     *********\n");
	printf("*****     3.div     *********\n");
	printf("*****     4.mul     *********\n");
	printf("*****     0.exit    *********\n");
}
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Div(int x, int y)
{
	return x / y;
}
int Mul(int x, int y)
{
	return x * y;
}
int main()
{
	int input = 0;
	int (*arr[5])(int, int) = { 0,Add,Sub,Div,Mul };
	do
	{
		int a = 0;
		int b = 0;
		int i = 0;
		printf("请输入两个数:\n");
		scanf("%d %d", &a, &b);
		printf("请选择你要进行的操作:\n");
		menu();
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			int ret = arr[input](a, b);//对函数指针数组的应用
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			;
		}
		else
			printf("输入错误,请重新输入\n");

	} while(input);
	return 0;
}

七、指向函数指针数组的指针

#include <stdio.h>
int main()
{
	
	int (*p)(int);//函数指针p

	int (*pp[5])(int);//函数指针的数组pp
	pp[0] = p;

	int (*(*ppp)[5])(int);//指向函数指针数组pp的指针ppp
	ppp = &pp;
	return 0;
}

八、回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
继续拿简易计算机来举例,回调函数的使用:
#include <stdio.h>
void menu()
{
	printf("******    1.Add     ******\n");
	printf("******    2.Sub     ******\n");
	printf("******    3.Div     ******\n");
	printf("******    4.Mul     ******\n");
	printf("******    0.exit    ******\n");
}
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Div(int x, int y)
{
	return x / y;
}
int Mul(int x, int y)
{
	return x * y;
}
void calc(int(*p)(int, int))//这就是回调函数的使用
{
	int a = 0;
	int b = 0;
	printf("请输入两个数:");
	scanf("%d %d", &a, &b);
	int ret = p(a, b);
	printf("ret = %d\n",ret);
}
int main()
{
	int input = 0;
	do 
	{
		menu();
		printf("请选择操作:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);//
			break;
		case 2:
			calc(Sub);//
			break;
		case 3:
			calc(Div);//
			break;
		case 4:
			calc(Mul);//
			break;
		case 0:
			printf("退出\n");
			break;
		default:
			printf("输入错误,重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

说到回调函数,这里介绍qsort函数的使用:

#include <stdio.h>
#include <stdlib.h>
int cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
int main()
{
	int arr[10] = { 5,3,7,6,2,9,1,8,4,10 };
	int len = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, len, sizeof(arr[0]), cmp);
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

这个函数能对所有类型的数据进行排序(字符型、整型、浮点型等等数据)

1.查看在MSDN上的描述,qsort函数的构成:

qsort(数组起始位置,数组元素个数,数组元素单位长度,比较函数)

2.查看在MSDN上的描述,比较函数的作用:

 该比较函数的作用为:传入函数的两个数据进行比较(设两个数据为elem1elem2),这两数据的类型与 排序数组的类型相同。

比较函数原理:

elem1 小于 elem2时,返回小于0的数

elem1 大于 elem2时,返回大于0的数

elem1 等于 elem2时,返回0

int cmp(const void* p1, const void* p2)
{
	if (*(int*)p1 > *(int*)p2)
	{
		return 1;
	}
	else if (*(int*)p1 == *(int*)p2)
	{
		return 0;
	}
	else
	{
		return -1;
	}
}
//int cmp(const void* p1, const void* p2)  这是简化版
//{
//	return (*(int*)p1 - *(int*)p2);
//}

这里对void*进行介绍:

1.void* 是一种无类型的指针,无具体类型的指针

2.void* 的指针变量可以存放任意类型的地址

3.void* 的指针不能直接进行解引用操作

4.void* 的指针不能直接进行+—整数

所以,比较函数cmp要使用传进来的数据时,要给数据加上强制类型转换。

例如比较字符数据和结构体数据时:

struct Stu {
	int age;
	char name[10];
};


//字符数据的比较函数
int cmp1(const void* p1, const void* p2)
{
	if (strcmp((char*)p1, (char*)p2) > 0)
	{
		return 1;
	}
	else if (strcmp((char*)p1, (char*)p2) == 0)
	{
		return 0;
	}
	else
	{
		return -1;
	}
}
//int cmp1(const void* p1, const void* p2)    这是简化版
//{
//	return strcmp((char*)p1, (char*)p2);
//}


//结构体数据的比较函数
int cmp2(const void* p1, const void* p2)
{
	if (strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name)>0)
	{
		return 1;
	}
	else if (strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name) < 0)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}
//int cmp2(const void* p1, const void* p2)       这是简化版
//{
//	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
//}

九、数组和指针部分笔试题分析

#include <stdio.h>
#include <string.h>
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//16  数组名a单独放在sizeof内部,计算的整个数组的大小
	printf("%d\n", sizeof(a + 0));// 4/8  a表示的首元素的地址,a+0还是数组首元素的地址
	printf("%d\n", sizeof(*a));//4
	printf("%d\n", sizeof(a + 1));//4
	printf("%d\n", sizeof(a[1]));//4
	printf("%d\n", sizeof(&a));// 4/8
	printf("%d\n", sizeof(*&a));//16
	printf("%d\n", sizeof(&a + 1));// 4/8
	printf("%d\n", sizeof(&a[0]));// 4/8
	printf("%d\n", sizeof(&a[0] + 1));// 4/8

	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//6
	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/8
	printf("%d\n", sizeof(&arr + 1));// 4/8
	printf("%d\n", sizeof(&arr[0] + 1));// 4/8
	printf("%d\n", strlen(arr));//随机值
	printf("%d\n", strlen(arr + 0));//随机值
	printf("%d\n", strlen(*arr));//err,strlen需要的是一个地址,从这个地址开始向后找字符,直到\0,统计字符的个数。
	//但是*arr是数组的首元素,也就是'a',这是传给strlen的就是'a'的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突

	printf("%d\n", strlen(arr[1]));//err,和上一个一样,内存访问冲突
	printf("%d\n", strlen(&arr)); //随机值,&arr是arr数组的地址,虽然类型和strlen的参数类型有所差异,但是传参过去后,还是从第一个字符的位置向后数字符,结果还是随机值。
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//随机值

	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));// 7
	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
	printf("%d\n", strlen(arr + 0));// 6
	printf("%d\n", strlen(*arr));// err,*arr是数组的首元素,也就是'a',这是传给strlen的就是'a'的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突
	printf("%d\n", strlen(arr[1]));// err,与上相同
	printf("%d\n", strlen(&arr));// 6
	printf("%d\n", strlen(&arr + 1));// 随机值,跳过该数组,直到碰到'\0'时停止
	printf("%d\n", strlen(&arr[0] + 1));// 5

	char* p = "abcdef";
	printf("%d\n", sizeof(p));// 4/8
	printf("%d\n", sizeof(p + 1));// 4/8
	printf("%d\n", sizeof(*p));// 1
	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));// 随机值,取的是指针变量p的地址,直到遇到'\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));// 48 数组名a单独放在sizeof内部,计算的整个数组的大小
	printf("%d\n", sizeof(a[0][0]));// 4
	printf("%d\n", sizeof(a[0]));// 16 a[0]表示第一行的数组名,数组名a[0]单独放在sizeof内部,计算的整个数组的大小
	printf("%d\n", sizeof(a[0] + 1));// 4/8 a[0]作为第一行的数组名,没有&,没有单独放在sizeof内部,所以a[0]是首元素地址
	//即a[0][0]的地址,a[0]+1就是第一行第二个元素的地址,是地址就是4/8。

	printf("%d\n", sizeof(*(a[0] + 1)));// 4  a[0]作为第一行的数组名,没有&,没有单独放在sizeof内部,所以a[0]是首元素地址
	//a[0]+1是第一行第二个元素的地址,*(a[0]+1)就是第一行第二个元素。

	printf("%d\n", sizeof(a + 1));// 4/8
	printf("%d\n", sizeof(*(a + 1)));// 16  *(a+1)就是第二行,相当于第二行数组名,*(a+1) -> a[1]

	printf("%d\n", sizeof(&a[0] + 1));// 4/8 有&,a[0]是第一行数组名,&a[0]为第一行的地址
	printf("%d\n", sizeof(*(&a[0] + 1)));// 16 *(&a[0] + 1)相当于第二行,也就是a[1]
	printf("%d\n", sizeof(*a));// 16  a作为二维数组的数组名,没有&,没有单独放在sizeof内部,所以a是首元素地址
	//*a就是二维数组的首元素,也就是第一行,*a -> a[0] -> *(a+0)

	printf("%d\n", sizeof(a[3]));// 16 越界了没关系,只跟属性有关
	//计算机不会记录数据类型怎么利用sizeof推断和计算大小
	return 0;
}

总结:

数组名的意义:

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。
*指针(地址)的大小是由平台决定,在32位的平台是4个字节,在64位的平台是8个字节。

十、指针笔试题

一、

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
int main()
{
	printf("%p\n", p + 0x1);//跳过一个结构体,结构体大小为20字节
	printf("%p\n", (unsigned long)p + 0x1);//将p强制转换为整型,整数+1,就是+1
	printf("%p\n", (unsigned int*)p + 0x1);//将p强制转换为int*型,p+1跳过int*个变量大小
	return 0;
}

二、

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  20000000
//  ptr1[-1] -> *(ptr1+(-1)) -> *(ptr1-1)
//  ptr2往后4个字节读取
	return 0;
}

 三、

#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	//逗号表达式  {    1   ,  3   ,  5   } 相当于数组里的元素为 1,3,5,0,0,0
	int* p;
	p = a[0];//a[0] -> a+0 -> a  a[0],没有&,也没有单独放在sizeof内部,所以a[0]为数组首元素地址
	printf("%d", p[0]);
	return 0;
}

四、

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]);
	// %p 地址相减  &p[4][2]-&a[4][2] ,小减大 ,为负数 ,-4
	//-4 数据在内存中以补码形式存在
	// -4 以地址的形式表现出来(十六进制)
	//10000000000000000000000000000100
	//11111111111111111111111111111011
	//11111111 11111111 11111111 11111100
	//   ff       ff       ff       fc
	//  %d    -4
	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  cpp往右移1
	//注意cpp移动的位置在下面也保持不变
	printf("%s\n", *-- * ++cpp + 3);//ER 适当加上括号理解(*-- (* ++cpp)) +3  cpp往右移1
	printf("%s\n", *cpp[-2] + 3);//ST cpp本身位置并不动
	printf("%s\n", cpp[-1][-1] + 1);//EW *((*cpp-1)-1)+1 
	return 0;
}

  • 19
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Cristiano777.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值