指针进阶大总结

目录

1.字符指针

2.指针数组

3.数组指针

3.1数组指针的定义

3.2 &数组名VS数组名

3.3 数组指针的使用

4. 数组参数、指针参数

4.1 一维数组传参

4.2 二维数组传参

4.3 一级指针传参

4.4 二级指针传参

5. 函数指针

6. 函数指针数组

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

8. 回调函数

9. 指针和数组笔试题解析

10. 指针笔试题


指针的概念:

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

内存会划分为小的内存单元,每个内存单元都有一个编号,这个编号就被称为地址,这个地址也叫指针

内存编号=地址=指针

指针或地址要存储就可以存放到指针变量中

int* p;

指针+-整数

指针-指针

指针的关系运算

1.字符指针

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

int main()
{
	char ch = 'w';
	char* pc = &ch;//1
	*pc = 'a';
	printf("%c\n", ch);

	return 0;
}
int main()
{
	const char* p = "abcdef";//常量字符串,不能被更改
//*p = 'w';//err

	//将字符串中的首字符的地址存入p中
	//p是指针变量,在x86环境下是4个字节

	
	printf("%c\n", *p);//a
	printf("%s\n", p);//abcdef
	return 0;
}
int main()
{
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";
	//两个字符串内容相同,不能更改只会在内存中存储一份地址

	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	//内存中会有两个数组,两份地址,所以两个首地址会不同

	if (p1 == p2)
	{
		printf("p1 == p2\n");
	}
	else
	{
		printf("p1 != p2\n");
	}

	if (arr1 == arr2)
	{
		printf("arr1 == arr2\n");
	}
	else
	{
		printf("arr1 != arr2\n");
	}

	return 0;

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

2.指针数组

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

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

//整型数组
//int arr[10];//存放整型的数组

//字符数组
//char arr2[5];//存放字符的数组
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int* p1 = &a;
	int* p2 = &b;
	int* p3 = &c;

	int* arr[3] = { &a,&b,&c };//arr就是一个指针数组

	return 0;
	
}
int main()
{
	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* parr[3] = { arr1,arr2,arr3 };//parr是指针数组
 //首元素地址属于int*
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//printf("%d", parr[i][j]);
   //parr[3] = {(1,2,3,4,5),(2,3,4,5,6),(3,4,5,6,7)};
   
			printf("%d",*(parr[i]+j));
		}
		printf("\n");
	}

	return 0;
}

3.数组指针

3.1数组指针的定义

int main()
{
	int a = 10;
	int* p = &a;//整型指针,指向整型的指针,存放整型变量的地址

	char ch = 'w';
	char* pc = &ch;//字符指针,指向字符的指针,存放的是字符变量的地址

	//数组指针,指向数组的指针
	int* p1[10];//p1是数组,指针数组
	int(*p2)[10];//p2是指针,指向一个数组十个元素每个元素是int,数组指针
//[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
	return 0;
}

3.2 &数组名VS数组名

int main()
{
	int a = 10;
	int* p = &a;

	int arr[10] = { 0 };
	//数组名是首元素地址

	printf("%p\n", arr);//0333F7B4  类型:int*
	printf("%p\n", arr + 1);//B8  +4 加了一个int的长度

	printf("%p\n", &arr[0]);//0333F7B4  类型:int*
	printf("%p\n", &arr[0] + 1);//B8  +4

	printf("%p\n", &arr);//0333F7B4  类型:int(*)[10]
	printf("%p\n", &arr + 1);//DC  +40 数组地址加一跳过一个数组
	
	int (*p)[10] = &arr;
	//p是一个指针,指向数组,p是一个数组指针

	return 0;
}
char* arr[5];
p = &arr;
char* (*p)[5] = &arr;

总结:数组名该怎么理解?

通常情况下,我们说的数组名都是数组首元素的地址

但是有两个例外:

  1. sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小
  2. &数组名,这里的数组名表示整个数组,&数组名,取出的是整个数组的地址

3.3 数组指针的使用

数组指针有何用?

//形参写出数组(形参和实参都是数组的形式)
void print1(int arr[],int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//形参写成指针的形式(实参是数组)
void print1(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(arr+i));
	}
	printf("\n");
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//写一个函数打印arr数组的内容
	int sz = sizeof(arr) / sizeof(arr[0]);
	print1(arr, sz);//首元素地址

	return 0;
}
void print1(int (*p)[10], int sz)//举例写法并不推荐
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//*p 相当于数组名,数组名又是首元素地址,所以*p就是&arr[0]
		printf("%d ", *(*p + i));
	}
	printf("\n");
}


int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//写一个函数打印arr数组的内容
	int sz = sizeof(arr) / sizeof(arr[0]);
	print1(&arr, sz);

	return 0;
}
//形参实参都是二维数组的形式
void print2(int arr[3][5], int c, int r)
{
	int i = 0;
	for (i = 0; i < c; i++)
	{
		int j = 0;
		for (j = 0; j < r; j++)
		{
			printf("%d ", arr[i][j]);

		}
		printf("\n");
	}
}

//形参为指针的形式
void print2(int (*p)[5], int c, int r)
{
	int i = 0;
	for (i = 0; i < c; i++)
	{
		int j = 0;
		for (j = 0; j < r; j++)
		{
			printf("%d ", *(*(p+i)+j));
			//printf("%d ",p[i][j]);
			//p+i是指向第i行的
			// *(p+i)相当于拿到第i行,也相当于第i行的数组名
			// 数组名表示首元素的地址,*(p+i)就是第i行第一个元素的地址

		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	//int (*ptr)[3][5] = &arr;
 //写一个函数,打印arr数组
	print2(arr, 3, 5);
	return 0;
}
int arr[5];//arr是一个整型数组,每个元素是int类型,有5个元素

int* parr1[10];//parr1是一个数组,数组10个元素,每个元素的类型是int*

int(*parr2)[10];//parr2是一个指向数组的指针,指向的数组有10个元素,每个元素的类型是int

int(*parr3[10])[5];//parr3是一个数组,数组有十个元素,每个元素的类型是int(*)[5]
//parr3是存放数组指针的数组

4. 数组参数、指针参数

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

4.1 一维数组传参

//形参写成数组的形式
void test(int arr[10])//本质是指针
{}
void test(int arr[])//形参部分的数组大小可以省略
{}
void test(int arr[100])//不建议但没错
{}

//形参写成指针的形式
void test(int* p)
{}

//
void test2(int* arr[20])
{}
void test2(int* arr[])
{}
void test2(int** p)
{}

int main()
{
	int arr[10] = { 0 };
    int  *arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

4.2 二维数组传参

//形参写成数组的形式
void test(int arr[3][5])
{}
void test(int arr[][5])//行可以省略但列不能省略
{}
void test(int arr[][])//err
{}

//形参写成指针的形式
void test(int* arr)//err
{}
void test(int* arr[5])//err
{}
void test(int(*arr)[5])
{}
void test(int** arr)//err  二级指针是用来存放一级指针的地址
{}
void test(int(*p)[5])//数组指针是用来存放数组的地址的
{}

4.3 一级指针传参

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 test(int* ptr)
{
	//...
}

int main()
{
	int a = 10;
	int* p = &a;
	int arr[10];
	test(arr);//数组
	test(&a);//p是一级指针
	test(p);

	return 0;
}

4.4 二级指针传参

void test(char** ppc)
{

}

//当函数的参数为二级指针的时候,可以接收什么参数?
int main()
{
	char ch = 'a';
	char* pc = &ch;
	char** ppc = &pc;

	char* arr[4];
	test(arr);

	//char arr2[3][5];
	//test(arr2);//err

	test(&pc);
	test(ppc);

	return 0;
}


//int main()
//{
//	char a = 'w';
//	char* pa = &a;
//	char** ppa = &pa;//ppa是一个二级指针
//
//	test(ppa);
//
//	return 0;
//}
void test1(int (*p)[5])
{}

void test2(int (*p)[3][5])
{
	*p;//代表的是第一行的地址
}

int main()
{
	int arr[3][5];
	test1(arr);//传递的是第一行的地址
	test2(&arr);//传递的是整个二维数组的地址,一般不使用这种写法
	return 0;
}

5. 函数指针

//函数指针   指向函数的指针
int Add(int x, int y)
{
	return x + y;
}

int test(char* str)
{

}

int main()
{
	int arr[10];
	int(*p)[10] = &arr;//p是一个数组指针变量,是用来存放数组的地址
//数组指针类型 int(*)[10]

	printf("%p\n", &Add);//&Add就能拿到函数的地址
	printf("%p\n", Add);
	//写法不同意义一样,都能拿到函数地址  

	int (*pf)(int,int) = Add;//pf就是函数指针变量
 //函数指针类型 int (*)(int,int)
	
	int (*pt)(char*) = test;

	return 0;

函数指针如何使用?

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int arr[10];
	int(*p)[10] = &arr;

	int (*pf)(int,int) = Add;//pf就是函数指针变量
 
 
	int ret = (*pf)(2, 3);//使用  这里的*是摆设,可写可不写,无意义,如果加*()不能省略
//(解引用)(调用函数)
  int ret = pf(2,3);
  
 //int ret = Add(2,3);//正常调用
 
	printf("%d\n", ret);
	

	return 0;
}

阅读两段有趣的代码:

int main()
{
	//假设0是一个地址
	//代码1
	//void (*)() 是函数指针类型
	//void (*p)()
	
	//(void (*)() )   (类型)强制类型转换
	//(*(void (*)())0    对0进行强制类型转换

	(*(void (*)())0)();
	//1.首先是把0强制类型转换为一个函数指针类型,意味着0地址处放一个返回类型是void,是一个无参的函数
	// 2.调用0地址处的这个函数
	



	//代码2
	//函数声明
	//int Add(int ,int)

	void (*signal(int, void(*)(int)))(int)
		
		typedef void(* pf_t)(int);//给函数指针类型void(*)(int);重新起名叫:pf_t
	pf_t signal(int, pf_t);
//signal函数返回类型  void(*)(int);
//signal是一个函数的声明
//signal函数的参数,第一个是int类型,第二个是void(*)(int)的函数指针类型

//简便写法
//void(*)(int) signal(int,void(*)(int))//err
		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* arr2[4];
	
	//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 };
//4可以省略

	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pf[i](8, 2);
		printf("%d\n", ret);
	}

	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;

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入2个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("请输入2个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("请输入2个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("请输入2个操作数:>");
			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;

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");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:>");
			scanf("%d%d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}

	} while (input);

	return 0;
}

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

int Add()
{
	return x + y;
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7 };
	int(*p)[10] = &arr;//p得是数组指针

	int* arr2[5];
	int* (*p2)[5] = &arr2;


	//函数指针
	int (*pf)(int, int) = &Add;

	//函数指针数组
	int (*pfarr[4])(int, int);
	int (* (*p3)[4])(int, int) = &pfarr;
	//p3是一个指向函数指针数组的指针

	return 0;
}

8. 回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应

//把一个函数地址传递给一个函数,这个函数内部通过一个指针去调用一个函数的时候
void test()
{
	printf("hehe\n");
}//回调函数

void print_hehe(void (*p)())
{
	if (1)
		p();//通过p指针调用函数 p指向test
}

int main()
{
	print_hehe(test);
	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");
}

//回调函数
void calc(int(*pf)(int,int))
{
	int x = 0;
	int y = 0;
	int ret = 0;

	printf("请输入2个操作数:>");
	scanf("%d%d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
	
}

int main()
{
	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:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

演示一下qsort函数的使用:

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-1-i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	//排序为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print_arr(arr, sz);
	return 0;

}

冒泡排序缺点,只能排整数序数,类型被定住了

qsort函数能排任意数

void qsort(void* base,//base中存放的是待排数据的起始位置
	size_t num,//num 数组元素个数
	size_t width,//width 一个元素的字节大小
	int(*cmp)(const void* e1, const void* e2)//函数指针
//cmp是比较函数   e1,e2是待比较的两个元素的地址
);

//比较函数返回三种值
//  <0
//  ==0
//  >0

比较函数的要求: qsort函数的使用者自定义一个 比较函数

排序的整型数据: < >

排序的结构体数据: 可能不方便直接使用<>

使用者需要根据实际情况提供一个函数,实现两个数据的比较

int mian()
{
    int a =10;
    float* pf = &a;//err
    void* pf = &a;//void* 可以存放任意类型的指针
    pf+1;//err  此时也不知道pf具体是什么类型所以无法对其进行计算
    *pf;//err  也无法进行解引用操作
    
    return 0;
}

所以qsort函数设计第一个就是void* 它可以存任意类型的指针

#include 

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int cmp_int(const void* e1, const void* e2)
{
	这种写法过于复杂
	//if (*(int*)e1 > *(int*)e2)
	//	return 1;
	//else if (*(int*)e1 == *(int*)e2)
	//	return 0;
	//else
	//	return -1;

	return (*(int*)e1 - *(int*)e2);
}

void test2()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//排序为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
	return 0;
}

int main()
{
	test2();
	return 0;
}
#include 

struct Stu
{
	char name[20];
	int age;
	double score;
};

int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->age , ((struct Stu*)e2)->age);
}
//使用qsort排序结构体
void test3()
{
	struct Stu arr[3] = { {"zhangsan",20,55.5},{"lisi",30,88.0},{"wangwu",49,72.2} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);;
	
}

int main()
{
	test3();
	return 0;
}

改进冒泡排序:

int cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

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-1-i; j++)
		{
			//if (arr[j] > arr[j + 1])//比较
            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[] = { 9,8,7,6,5,4,3,2,1,0 };
	//排序为升序
	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;
}
void test5()

{

	struct Stu arr[3] = { {"zhangsan", 20, 55.5},{"lisi", 30, 88.0},{"wangwu", 10, 90.0} };

	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);

	//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);

}

9. 指针和数组笔试题解析

//数组名是什么呢?
//数组名通常表示数组首元素的地址
//两个例外:
// 1.sizeof(数组名)  这里的数组名表示整个数组,计算的是整个数组的大小
//2.&数组名   这里的数组名表示整个数组,取出的是整个数组的地址
//


int main()
{
	//一维数组
	int a[] = { 1,2,3,4 };
	//          0 1 2 3

	printf("%d\n", sizeof(a));//4*4=16
	printf("%d\n", sizeof(a + 0));//4/8  a+0是数组第一个元素的地址,是地址大小就是4/8个字节
	printf("%d\n", sizeof(*a));//4  a表示数组首元素的地址,*a表示数组第一个元素,sizeof(*a)就是第一个元素的大小
	printf("%d\n", sizeof(a + 1));//4/8  a表示数组首元素的地址,a+1是数组第二个元素的地址,sizeof(a + 1)就是第二个元素的地址
	printf("%d\n", sizeof(a[1]));//4  计算第二个元素的大小
	printf("%d\n", sizeof(&a));//4/8 &a取出的是整个数组的地址,数组的地址也是地址,是地址大小就是4/8个字节
	printf("%d\n", sizeof(*&a));//16  解引用拿到整个数组的地址,计算的是整个数组的大小,可以看做是sizeof(a)
	printf("%d\n", sizeof(&a + 1));//4/8  &a是整个数组的地址,+1就是跳过整个数组,产生4后面位置的地址
	printf("%d\n", sizeof(&a[0]));//4/8 取出的是数组第一个元素的地址
	printf("%d\n", sizeof(&a[0] + 1));//4/8 数组第二个元素的地址

	return 0;
}

//sizeof计算的是对象所占内存大小,单位是字节
int main()
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };//[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是首元素,首元素是一个字符,大小是一个字节
	printf("%d\n", sizeof(arr[1]));//1  arr[1]是数组第二个元素,大小是一个字节
	printf("%d\n", sizeof(&arr));//4/8  &arr是数组的地址
 printf("%d\n", sizeof(&arr + 1));//4/8 &arr+1 是从数组地址开始向后跳过整个数组产生的一个地址
	printf("%d\n", sizeof(&arr[0] + 1));//4/8 &arr[0]+1 是数组第二个元素的地址

	printf("%d\n", strlen(arr));//随机值   arr数组中没有\0,所以strlen函数会继续往后找\0,统计\0之前出现的字符个数
	printf("%d\n", strlen(arr + 0));//随机值   arr+0还是数组首元素的地址
	printf("%d\n", strlen(*arr));//err  arr是数组首元素地址,*arr是数组首元素。'a'-97 可能会出现野指针问题 97不是地址
	printf("%d\n", strlen(arr[1]));//err  ‘b'-98
	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//随机值


	return 0;
}
int main()
{

	char arr[] = "abcdef";//[a b c d e f \0]

	printf("%d\n", sizeof(arr));//7
	printf("%d\n", sizeof(arr + 0));//4/8 arr+0是数组首元素地址
	printf("%d\n", sizeof(*arr));//1  *arr数组的首元素
	printf("%d\n", sizeof(arr[1]));//1 arr[1]是数组的第二个元素
	printf("%d\n", sizeof(&arr));//4/8  &arr数组的地址
	printf("%d\n", sizeof(&arr + 1));//4/8 &arr+1是\0后边的地址,它跳过整个数组
	printf("%d\n", sizeof(&arr[0] + 1));//4/8  &arr[0] + 1是数组第二个元素的地址


	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//6
	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));//5  第二个元素之后,\0之前


	return 0;
}

sizeof是一个操作符

计算的是丢向所占内存大小,单位是字节 size_t

不在乎内存中存放的是什么,只在乎内存大小

strlen 库函数

求字符串长度,从给定的地址向后访问字符,统计\0之前出现的字符个数

int mian()
{
	char* p = "abcdef";//p中存放的是a的地址

	printf("%d\n", sizeof(p));//4/8  p是指针变量,计算的是指针变量的大小
	printf("%d\n", sizeof(p + 1));//4/8  p+1是b的地址
	printf("%d\n", sizeof(*p));//1  *p是’a‘
	printf("%d\n", sizeof(p[0]));//1  其实是*(p+0)也就是*p
	printf("%d\n", sizeof(&p));//4/8  取地址  &p是指针变量p在内存中的地址
	printf("%d\n", sizeof(&p + 1));//4/8  &p+1是跳过p之后的地址
	printf("%d\n", sizeof(&p[0] + 1));//4/8 &p[0]是a的地址,&p[0] + 1是b的地址


	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5  从b的位置开始向后数字符
	printf("%d\n", strlen(*p));//err
	printf("%d\n", strlen(p[0]));//err
	printf("%d\n", strlen(&p));//随机值  p中放的是指针变量的地址不是字符
	printf("%d\n", strlen(&p + 1));//随机值
	printf("%d\n", strlen(&p[0] + 1));//5  从b的位置开始向后数字符

	return 0;
}

二维数组的理解:

可以把二维数组想象成一维数组,二维数组的每一行是一个一维数组的

int main()
{
	//二维数组
	int a[3][4] = { 0 };
	//访问第一行  a[0][i]  第一行数组名  a[0]
	//访问第二行  a[1][i]  第二行数组名  a[1]
	//访问第三行  a[2][i]  第三行数组名  a[2]


	printf("%d\n", sizeof(a));//4*3*4=48  计算的是整个数组的大小
	printf("%d\n", sizeof(a[0][0]));//4 第一行第一个元素的大小
	printf("%d\n", sizeof(a[0]));//16  a[0]是第一行的数组名,sizeof(a[0])就是第一行的数组名单独放在sizeof内部,计算的是第一行的大小
	printf("%d\n", sizeof(a[0] + 1));//4/8  a[0]作为第一行的数组名,并没有单独放在sizeof内部,也没有被取地址
	//所以a[0]就是数组首元素的地址,就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址
	printf("%d\n", sizeof(*(a[0] + 1)));//4  *(a[0] + 1)表示的是第一行第二个元素
	printf("%d\n", sizeof(a + 1));//4/8  a表示首元素的地址,a是二维数组,首元素的地址就是第一行的地址
	//所以a表示的是二维数组第一行的地址,a+1就是第二行的地址
	printf("%d\n", sizeof(*(a + 1)));//16  *(a + 1)表示的是第二行   等价于a[1]
	printf("%d\n", sizeof(&a[0] + 1));//4/8  a[0]是第一行的数组名,&a[0]取出的就是第一行的地址,(&a[0] + 1)就是第二行的地址
	printf("%d\n", sizeof(*(&a[0] + 1)));//16  对第二行的地址解引用访问到的就是第二行
	printf("%d\n", sizeof(*a));//16  等价于*(a+0) 也就是a[0]   a是首元素地址,就是第一行地址,*a就是第一行
	printf("%d\n", sizeof(a[3]));//16 int
	
	
	return 0;
}

10. 指针笔试题

//1

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);//强制类型转换成int*  ptr指向的是5之后
	//&a-->int(*)[5]数组指针类型
	printf("%d,%d", *(a + 1), *(ptr - 1));//2,5   ptr-1指向的是从5开始

	return 0;
}
//程序的结果是什么?
//2
//编译环境是x86
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;

//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节

int main()
{
	p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);//00100014   0x100000+20
	printf("%p\n", (unsigned long)p + 0x1);//00100001  整型+1=+1
	printf("%p\n", (unsigned int*)p + 0x1);//00100004  整型指针+1=+4

	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  小端

	return 0;
}
//4
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	//逗号表达式      1        3       5
	//13
	//50
	//00
	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));//aa[1]

	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10,5

	return 0;
}
//7
int main()
{
	char* a[] = { "work","at","alibaba" };
	//work\0
	//at\0
	//alibaba\0
	char** pa = a;//首元素
	pa++;

	printf("%s\n", *pa);//at 解引用向后访问一个字母 %s打印字符串只要给出首字母地址

	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;
}
//a[1]-->*(a+1)

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