指针进阶(C语言)

目录

字符指针

使用方法:

1、指向字符

2、指向字符串的首地址

指针数组

数组指针

数组指针的定义

数组名表示的含义

数组指针的使用

数组参数、指针参数

一位数组传参

二维数组传参

一级指针传参

二级指针传参

函数指针

函数指针数组

指向函数指针数组的指针

回调函数

1、如何使用qsort:

2、模拟实现qsort:

指针和数组笔试题解析

指针变量笔试题

第一题:

第二题:

第三题:

第四题:

第五题:

第六题:

第七题:

第八题:


由初阶指针可知:

1、指针就是地址

2、指针变量是存放指针(地址)的变量

3、我们在口语上把指针变量也叫指针

      所以要严格区分指针和指针变量

4、指针的大小是固定的32位:4 / 64位:8个字节

5、指针变量的类型:

      指针的类型加减整数的步长

      解引用操作访问空间的权限

6、指针的运算

      指针-指针,可计算出两个指针之间的元素个数

      指针与指针可比较大小

这篇文章带你更深入的了解指针

字符指针

顾名思义字符指针就是指向字符的指针

我们知道指针是有类型的

而字符指针的类型:char*

字符指针的两种使用方法:

1、指向字符

2、指向字符串的首元素地址

使用方法:

1、指向字符

就是字符指针变量里面存的是一个字符的地址

可通过解引用操作来访问这个字符

#include 

int main()
{
	char c = 'a';
	char* p = &c;

	//通过指针解引用取到字符
	printf("%c\n", *p);

	//通过指针修改字符
	*p = 'b';
	printf("%c\n", c);

	return 0;
}

2、指向字符串的首地址

字符指针变量指向字符串的首地址

字符指针变量里面存放的是字符串的首元素地址

可通过字符指针访问整个字符串

用%s打印整个字符串:

    %s打印的时候从起始位置开始直到遇到'\0'结束

#include 
int main()
{
	const char str[] = "hello";

	//数组名表示首元素的地址
	//p里面存放的是字符串首元素的地址
	//也就是字符'h'的地址
	char* p = str;
	//通过指针p访问整个字符串
	printf("%s\n", p);

	return 0;
}

一道典型笔试题:

#include 
int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char* p = "hello world";
	const char* q = "hello world";

	//str1 和 str2 是不同的两块空间
	//数组名表示首元素地址
	//所以str1的首元素地址,跟str2的首元素地址不相同
	if (str1 == str2)
	{
		printf("st1 等于 str2\n");
	}
	else
	{
		printf("str1 不等于 str2\n");
	}

	//因为hell world 是常量字符串,存放在内存常量区
	//指针 p 和 q 都指向这个常量字符串的首地址
	// 所以p 和 q相同
	if (p == q)
	{
		printf("p 等于 q\n");
	}
	else
	{
		printf("p 不等于 q\n");
	}

	return 0;
}

指针数组

指针数组本质上是一个数组

而数组里面存放的是指针

int* p[5];  --> 指针数组

因为[]的优先级高于* 

所以p先和[]结合,是一个数组

该数组里面有5个元素,每个元素是int*类型的

#include 
int main()
{
	//整型指针数组
	int* arr[10];
	
	//浮点型指针数组
	float* frr[10];

	//字符型指针数组
	 char* str1[10];
	//我们知道用一个字符指针可以指向一个字符串的首地址
	//然后可通过这个指针访问整个字符串
	//我们可以给字符指针数组这样赋值
	char* str[] = { "abcd","efgh","lmno","pqrstuv" };
	int sz = sizeof(str) / sizeof(str[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s\n", str[i]);
	}

	return 0;
}

数组指针

数组指针本质上一个指针

而这个指针指向的是数组

指针里面存放的是数组的地址

数组指针的定义

我们知道指针表示:

整型指针:int* p; 能够指向整型的指针

字符型指针:char* p; 能够指向字符型的指针

浮点型指针:float* p; 能够指向字符型的指针

那么数组指针的类型我们该怎么写呢?

int(*p)[5]; -- >数组指针  (整型数组指针)

[]的优先级要高于*号的,所以必须加上()来保证p先和*结合

*号先和p结合说明是一个指针,除去*和p就是指针所指向的类型

该指针指向的数组总共有五个元素,且每个元素都是int*类型的

#include 
int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };

	//定义一个整型数组指针,并将这个整型数组的地址赋给指针
	int(*p)[10]=&arr;

	char str[10] = "abcdef";

	//定义一个字符数组指针,并将这个字符数组的地址赋值给q;
	char(*q)[10] = &str;

	float frr[5] = { 1.2,1.3,1.5,1.6,0.8 };

	//定义一个浮点型数组指针,并将浮点型数组的地址赋值给指针
	float(*f)[5] = &frr;

	//去掉*和指针变量名,就是指针所指向的类型

	return 0;
}

数组名表示的含义

数组名单独出现的情况:

数组名只有在两种情况下表示整个数组

sizeof(数组名) 和   &数组名 表示整个数组

其它情况下数组名都表示首元素的地址

#include 
int main()
{
	int arr[10] = { 0 };

	//数组名
	printf("%p\n", arr);//表示首元素地址
	printf("%p\n", &arr);//表示整个整租的地址

	//地址+1的步长就能看出地址是数组的地址还是首元素地址
	printf("%p\n", arr + 1);
	printf("%p\n", &arr + 1);

	return 0;
}

数组指针的使用

数组指针是指向数组的指针

那具体改怎么使用呢?

看以下代码:

#include 
void print(int(*arr)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			//printf("%d ", arr[i][j]);
			printf("%d ", *(*(arr + i) + j));// *(arr+i) 得到每一行首元素的地址
		}
		printf("\n");
	}
}

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

	int a[2][5] = { {1,2,3,4,5},{6,7,8,9,10} };

	//数组名表示首元素的地址,二维数组首元素是一个一维数组
	//也就是二维数组第一行(一位数组)的地址
	print(a, 2, 5);
	//传过去的是一个一位数组的地址,用一个数组指针来接收
	return 0;
}

int arr[5];  ---->  整型数组
int *parr1[10]; ------> 指针数组
int (*parr2)[10];------> 数组指针
int (*parr3[10])[5]; -------> 数组指针数组

数组参数、指针参数

当我们把数组或者指针传给函数时

函数的参数改如何设计呢?

下面我们仔细了解一下函数参数的设计

一位数组传参

一位数组在传参的时候

传过去的是数组名

也就是数组首元素的地址

因为传的是首元素的地址

我们在接收的时候用指针接收

在书写的时候可以写成指针的形式

也可以写成数组的形式

#include 
//以指针的形式接收
void print_arr(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
//以数组的形式接收
void print2_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	int arr[] = {1,2,3,4,5};
	int sz = sizeof(arr) / sizeof(arr[0]);
	
	//指针形式接收
	print_arr(arr, sz);
	printf("\n");

	//数组形式接收
	print2_arr(arr, sz);
	return 0;
}

二维数组传参

二维数组传参的时候

传的是数组名

数组名表示首元素地址

二维数组的首元素表示第一行(一维数组)

所以数字名表示的是一维数组的地址

在传参的时候传过去的是第一行一维数组的地址

在接收的时候用数组指针接收

也可用二维数组的形式接收,但二维数组的列不可以省略!

#include 
//用数组指针接收
void print_arr(int(*arr)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
		//	printf("%d ", arr[i][j]);
			printf("%d ", *(*(arr + i) + j));//arr[0] 起始就是第一行一维数组的数组名
		}
		printf("\n");
	}
}

//用二维数组形式接收,二维数组的行不可省略
void print2_arr(int arr[][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{

	int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };

	print_arr(arr, 3, 5);

	return 0;
}

一级指针传参

指针传参就用指针接收

当函数参数是一个一级指针时我们能传什么过去?

1、可以传一级指针变量

2、可传变量的地址

3、可传一维数组

#include 
void print(int *p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(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;
}

二级指针传参

二级指针传参用二级指针接收

当函数参数是一个二级指针时我们能传什么过去?

1、可以传二级指针变量

2、可以传一级指针变量的地址

3、可以传指针数组

#include 
void print(char** p,int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s\n", p[i]);
	}
}

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

	char* str[] = { "abcd","efg","hij" };
	int sz = sizeof(str) / sizeof(str[0]);
	print(str, sz);

	return 0;
}

函数指针

我们所知的函数也是有地址

为什么函数也有地址呢?

因为函数有自己的函数栈帧

函数栈帧是占用内存空间的

而内存中每个字节都有自己特定的编号(也就是地址)

由此我们可推断出函数也有地址

那如何取到函数的地址呢?

        1、&函数名 可以取到函数的地址

        2、函数名 表示函数的地址

如何存放函数的地址?

  打个比方:

     整型指针整型变量的地址

     浮点型指针存放浮点型变量的地址

     字符型指针存放字符型变量的地址

........

由此可知:

        用函数指针来存放函数的地址

函数指针的类型该怎么写呢?

函数的返回类型,函数的参数都要写在类型中

返回类型 *指针变量名 ( 参数类型 )

假设函数:

返回类型为 int, 参数为int

则它的指针类型为:int (*p) ( int ,int );

p先和*号结合说明是一个指针

而指针指向的类型为返回值为int,参数为:int,int的函数类型

返回类型为:float, 函数参数为两个(int ,double)

则它的指针类型为:float(*p) ( int , double )

..........

两个出自《C指针与缺陷》的典型代码:

1、(*(void (*)( ) ) 0 ) ();

将数字0先强制类型转化成函数指针(void(*)())类型

再以指针的方式调用0地址处的函数(会报错)

2、void (*signal(int , void(*)(int)))(int);

signal与()先结合,说明是一个函数

函数的参数有两个分别是:int 和 void(*)(int)

函数的返回类型是:void(*)(int);

参数是(一个整型)和(一个指向返回值为void 参数为int 的函数指针)

返回值是:一个指向返回值为void 参数为int 的函数指针

#include 

void test(int x)
{
	printf("%d\n", x);
}

int main()
{
	int n = 19;
	test(n);

	//取到函数的地址
//  1、&test;
//  2、test;
	
	//将函数的地址用函数指针变量存储起来
	void (*p) (int) = test;
	void (*q) (int) = &test;

	//通过函数指针调用函数
	int a = 10;
	//第一种调用函数
	p(a);
	//第二种调用函数
	int b = 20;
	(*q)(b);

	//其实调用的时候函数指针变量前面的*号可以省略
	int c = 40;
	q(c);
	//其实这个*号也可当成是一种摆设,不管加几个都可以调用
	int d = 50;
	(********q)(d);//加多少个*号都可以调用!

	return 0;
}

函数指针数组

我们知道数组是相同元素的集合

也就是相同类型的空间

数组里面的元素,可以是整型、浮点型、字符型

也可以是指针类型:

int* arr[10]; --> 整型指针数组

数组里面存放10个元素,每个元素的类型是int*

而存放函数地址的数组被称为,函数指针数组

函数指针数组的定义:

int (*ptr[5]) (int,int);  -->函数指针数组

ptr先和[]结合是一个数组,数组里面有五个元素

每个元素的类型是:int(*) (int,int)

(也就是指向返回值为int 两个参数都为int的函数指针)

函数指针的用途:转移表

看以下代码:

#include 

//例子:计算器

//按正常繁琐思路实现

void mnue(void)
{
	printf("*************************\n");
	printf("**** 1:add  2:sub ******\n");
	printf("**** 3:mul  4:dvi ******\n");
	printf("****** 0:exit **********\n");
	printf("*************************\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 dvi(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	do {
		mnue();//菜单
		printf("请选择--> ");
		scanf("%d", &input);
		int ret = 0;
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			ret = add(x,y);
			printf("%d + %d = %d\n",x,y, ret);
			break;
		case 2:
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			 ret = sub(x, y);
			printf("%d - %d = %d\n", x, y, ret);
			break;
		case 3:
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
		    ret = mul(x, y);
			printf("%d * %d = %d\n", x, y, ret);
			break;
		case 4:
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			ret = dvi(x, y);
			printf("%d / %d = %d\n", x, y, ret);
			break;
		case 0:
			printf("退出成功\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);

	return 0;
}






//按照函数指针数组实现

void menu(void)
{
	printf("*************************\n");
	printf("**** 1:add  2:sub ******\n");
	printf("**** 3:mul  4:dvi ******\n");
	printf("****** 0:exit **********\n");
	printf("*************************\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 dvi(int x, int y)
{
	return x / y;
}

//创建一个函数指针数组
//转移表
int (*p[5])(int, int) = { NULL,add,sub,mul,dvi };
int main()
{
	int x = 0;
	int y = 0;
	int ret = 0;
	int input = 0;
	do {
		menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = p[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出成功\n");
		}
		else
		{
			printf("输入有误,请重新输入\n");
		}
	} while (input);

	return 0;
}

指向函数指针数组的指针

那么什么是指向函数指针数组的指针呢?

我们慢慢分析这句话

它是一个数组指针

而这个指针指向一个数组

数组里面的元素类型是函数指针

也就是一个指向数组的数组指针而数组里面存放的是函数指针

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

#include 

void test(const char* str)
{
	printf("%s\n", str);
}

int main()
{
	char* str = "hello";

	//函数指针
	void(*p) (const char*) = test;

	//函数指针数组
	void(*q[5])(const char*);
	q[0] = test;

	//指向函数指针数据的指针
	void (*(*ptr)[5]) (const char*);
	//ptr先与*结合 是一个指针,再与括号结合 是一个指向数组的数组指针
// ptr指向一个数组 数组里面有五个元素,每个元素的类型是一个返回值为空,参数为char*的函数指针

	return 0;
}

回调函数

回调函数就是一个通过函数指针调用的函数。

如果你把函数的指针(地址)作为参数传递给另一个函数,

当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,

而是在特定的事件或条件发生时由另外的一方调用的,

用于对该事件或条件进行响应。

回调函数例子:qsort函数

如下代码:

1、如何使用qsort:

#include 

//qsort函数的使用

//qsort函数的原型:
//void qsort(void* base,
//		    size_t num,
//		    size_t size,
//	        int(*compar)(const void*, const void*));
//第一个参数是要排序的数组
//第二个参数是数组的元素个数
//第三个参数是数组的单个元素的大小
//第四个元素是一个函数指针,返回值为int 两个参数为const void* 的函数,这个函数是自己实现的

//定义结构体
struct ss {
	char name[10];
	int age;
};

//排序整型
int com_int(const void* e1, const void* e2)
{
	//升序:从小到大排序
	//return *((int*)e1) - *((int*)e2);

	//降序:从大到小排序
	return *((int*)e2) - *((int*)e1);
}

//排序字符
int com_char(const void* e1, const void* e2)
{
	//升序,从小到大排序
	return *((char*)e1) - *((char*)e2);

	//降序,从大到小排序
	//return *((char*)e2) - *((char*)e1);
}

//按照age排序结构体
int com_age(const void* e1, const void* e2)
{
	//升序排序
	//return ((struct ss*)e1)->age - ((struct ss*)e2)->age;

	//降序排序
	return ((struct ss*)e2)->age - ((struct ss*)e1)->age;
}

//按照name排序
int com_name(const void* e1, const void* e2)
{
	//升序排序
	return strcmp(((struct ss*)e1)->name , ((struct ss*)e2)->name);

	//降序排序
	//((struct ss*)e2)->name - ((struct ss*)e1)->name;
}
int main()
{
	//qsort排序整型
	int arr[] = { 3,5,4,1,2 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), com_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	//qsort排序字符
	char str[] = "bdfeca";
	sz = strlen(str);
	qsort(str, sz, sizeof(str[0]), com_char);
	printf("%s\n", str);


	//qsort 排序结构体
	struct ss aa[3] = { {"zhangsan",25},{"wangwu",36},{"lisi",20} };//定义结构体变量
	sz = sizeof(aa) / sizeof(aa[0]);
	//用年龄排序
	qsort(aa, sz, sizeof(aa[0]), com_age);
	for (i = 0; i < sz; i++)
	{
		printf("%s  %d\n", aa[i].name, aa[i].age);
	}
	printf("\n");
	//用名字排序
	qsort(aa, sz, sizeof(aa[0]), com_name);
	for (i = 0; i < sz; i++)
	{
		printf("%s   %d\n", aa[i].name, aa[i].age);
	}
	return 0;
}

2、模拟实现qsort:

#include 

//用冒泡排序算法 模拟实现qsort函数

qsort函数的原型:
void qsort(void* base,
		    size_t num,
		    size_t size,
	        int(*compar)(const void*, const void*));

int com_int(const void* e1, const void* e2)
{
	//升序 从小到大
	//return *((int*)e1) - *((int*)e2);

	//降序 从大到小
	return *((int*)e2) - *((int*)e1);
}

int com_char(const void* e1, const void* e2)
{
	//升序 从小到大
	return *((char*)e1) - *((char*)e2);

	//降序 从大到小
	//return *((char*)e2) - *((char*)e1);
}

void swap(void* p1, void* p2, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)p1+i);
		*((char*)p1 + i) = *((char*)p2 + i);
		*((char*)p2 + i) = tmp;
	}
}

void my_qsort(void* be, size_t sz, size_t size, int(*p)(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 (p((char*)be + j * size, (char*)be + (j + 1)*size) > 0)
			{
				//交换
				swap((char*)be + j * size, (char*)be + (j + 1)*size, size);
			}
		}
	}
}

int main()
{
	//排序整数
	int arr[] = { 3,5,1,4,2 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_qsort(arr, sz, sizeof(arr[0]), com_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");

	//排序字符型
	char str[] = "dabefc";
	sz = strlen(str);
	qsort(str, sz, sizeof(str[0]), com_char);
	printf("%s\n", str);

	return 0;
}

指针和数组笔试题解析

下面有几道笔试题供大家观看

我们以代码的方式,注释讲解

代码如下:

#include 

//第一题

int main()
{
	//一维数组
	//sizeof()操作符用来数据计算占用内存空间的大小单位是字节
	//sizeof() 括号中的表达式是不参与运算的
	//地址(指针)的大小在32位平台下是4字节,在64位平台下是8字节
	int a[] = { 1,2,3,4 };

	printf("%d\n", sizeof(a));//sizeof(数组名)数组名单独出现,代表整个数组,计算整个数组的大小,大小为:16

	printf("%d\n", sizeof(a + 0));//数组名a表示首元素地址,让地址+0 等同于没加 还是地址,地址的大小为 :4/8

	printf("%d\n", sizeof(*a));//数组名a表示首元素地址,指向首元素,给指针解引用取到的是首元素的内容 1,数字1是整型大小为4个字节

	printf("%d\n", sizeof(a + 1));//数组名表示首元素的地址 让首地址+1 取到第二个元素的地址,还是地址,地址的大小为:4/8 字节

	printf("%d\n", sizeof(a[1]));// a[1] 等同于 *(a+1),表示第二个元素 数字2,数字2是int类型,int的大小为4字节

	printf("%d\n", sizeof(&a));// &a 取到的是整个数组的地址,但本质还是地址,地址的大小为:4/8 字节

	printf("%d\n", sizeof(*&a));//*&a 给整个数组的地址进行解引用取到的是整个数组的内容,*&a 等同于 a 表示整个数组,大小为:16

	printf("%d\n", sizeof(&a + 1));//&a取到数组的地址,让数组的地址+1 还是地址,地址的大小为:4/8 字节

	printf("%d\n", sizeof(&a[0]));//&a[0] 取到的是首元素的地址 等同于 &*(a+0) 还是地址 地址的大小为:4/8字节

	printf("%d\n", sizeof(&a[0] + 1));// &a[0]取到首元素的地址,首元素地址+1 还是地址 地址的大小为:4/8字节

	return 0;
}



// 第二题

int main()
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };

	printf("%d\n", sizeof(arr));//6 sizeof(数组名) 单独出现表示整个数组 大小为:6 字节

	printf("%d\n", sizeof(arr + 0));// 4/8 arr表示首元素地址,aar+0 还是首元素地址,地址大小为:4/8 字节

	printf("%d\n", sizeof(*arr));// 数组名表示首元素地址 对数组名进行解引用,取到首元素内容,大小为:1字节

	printf("%d\n", sizeof(arr[1]));// arr[1] 等同于 *(arr+1) 取到首元素内容  大小为:1字节

	printf("%d\n", sizeof(&arr));//&arr 表示整个数组的地址,还是地址,大小为:4/8 字节

	printf("%d\n", sizeof(&arr + 1));// &arr 表示整个数组的地址,&arr+1 跳过整个数组指向下一片空间,还是地址 大小为:4/8 字节

	printf("%d\n", sizeof(&arr[0] + 1));// &arr[0] 取到首元素地址,让首元素地址+1 是第二个元素的地址,本质还是地址,大小为:4/8 字节

	
	// strlen 函数计算字符串的长度,遇到'\0'停止
	// strlen 只计算’\0'之前的个数
	// strlen 的参数是一个指针,也就是要传一个地址进去
	// 从传进去的地址开始往后计算 碰到 \0 结束

	printf("%d\n", strlen(arr));// 随机值 因为arr字符数组里面没有'\0' 所以不知道会到那个位置停止

	printf("%d\n", strlen(arr + 0));// arr表示首元素地址,arr+0还是首元素地址,结果还是随机值

	printf("%d\n", strlen(*arr));// err 报错,因为传进去的是 一个字符‘a' ASICC值为97,将97解读成地址传过去,非法的

	printf("%d\n", strlen(arr[1]));// err 报错,传进去的还是第一个元素,97 非法

	printf("%d\n", strlen(&arr));// 随机值,&arr表示整个数组的地址,但整个数组的地址还是取到数组最低位的地址,最低位的地址就是首元素的地址,从首元素的位置开始计算

	printf("%d\n", strlen(&arr + 1));//随机值 &arr表示整个数组的地址,&arr+1 取到数组后面空间的地址,从后面空间开始计算

	printf("%d\n", strlen(&arr[0] + 1));//随机值 &arr[0] 取到首元素地址 &arr[0]+1 取到第二个元素的地址,从第二个元素地址开始计算

	return 0;
}




//第三题

int main()
{
	//	//sizeof()操作符用来数据计算占用内存空间的大小单位是字节
//	//sizeof() 括号中的表达式是不参与运算的
//	//地址(指针)的大小在32位平台下是4字节,在64位平台下是8字节

	char arr[] = "abcdef";

	printf("%d\n", sizeof(arr));//sizeof(数组名)表示整个数组,整个数组的大小为:7

	printf("%d\n", sizeof(arr + 0));// arr表示首元素地址 arr+0还是首元素地址 本质是地址 大小为:4/8 字节

	printf("%d\n", sizeof(*arr));// arr表示首元素地址,*arr 取到首元素内容 ’a‘ 大小为:1 字节

	printf("%d\n", sizeof(arr[1]));// arr[1] 取到首元素内容 ’a' 大小为:1 字节

	printf("%d\n", sizeof(&arr));// &arr 表示整个数组的地址,本质上是地址 大小为:4/8 字节

	printf("%d\n", sizeof(&arr + 1));// &arr 表示整个数组的地址,&arr+1 指向数组后面的一片空间,还是地址,大小:4/8 字节

	printf("%d\n", sizeof(&arr[0] + 1));// &arr[0] 取到的是首元素的地址 &arr[0]+1 指向第二个元素,还是地址,大小:4/8 字节


	//	// strlen 函数计算字符串的长度,遇到'\0'停止
//	// strlen 只计算’\0'之前的个数
//	// strlen 的参数是一个指针,也就是要传一个地址进去
//	// 从传进去的地址开始往后计算 碰到 \0 结束

	printf("%d\n", strlen(arr));// arr表示首元素地址,从首地址开始往后数 遇到\0 结束 6

	printf("%d\n", strlen(arr + 0));//arr表示首元素地址,arr+0还是首元素地址,6

	printf("%d\n", strlen(*arr));// arr表示首元素地址 *arr是首元素内容,‘a' 97 非法 err报错
	
	printf("%d\n", strlen(arr[1]));//arr[1]表示首元素内容 ’a' 97解读成地址传过去  非法 err报错

	printf("%d\n", strlen(&arr));//&arr表示整个数组的地址,但整个数组的地址还是取到数组最低位的地址,最低位的地址就是首元素的地址,从首元素的位置开始计算 6

	printf("%d\n", strlen(&arr + 1));// &arr表示整个数组的地址,&arr+1 指向数组后面的一片空间,开始计算,随机值

	printf("%d\n", strlen(&arr[0] + 1));//&arr[0]表示首元素地址,&arr[0]+1 取到第二个元素地址,从第二个元素地址开始计算 5

	return 0;
}



//第四题
int main()
{
	char *p = "abcdef";

	printf("%d\n", sizeof(p));//p是一个指针变量,指针就是地址 地址的大小为:4/8 字节

	printf("%d\n", sizeof(p + 1));// p+1取到'b'的地址,还是地址 大小为:4/8 字节

	printf("%d\n", sizeof(*p));// *p p里面存放的是’a'的地址,*p取到'a' 大小为:1 字节

	printf("%d\n", sizeof(p[0]));// p[0] 等同于 *(p+0) 也就是*p 还是取到’a' 大小为:1 字节

	printf("%d\n", sizeof(&p));// &p 取到的是指针变量p的地址,是一个二级指针,还是地址,大小为:4/8 字节

	printf("%d\n", sizeof(&p + 1));// &p 取到的是指针变量p的地址,是一个二级指针 让二级指针+1 还是地址 大小为:4/8 字节

	printf("%d\n", sizeof(&p[0] + 1));// &p[0] 取到’a'的地址 让地址+1 取到‘b'的地址,还是地址 大小为:4/8 字节


	printf("%d\n", strlen(p));// p指向’a' p是‘a'的地址,从’a'开始计算 6

	printf("%d\n", strlen(p + 1));//  p指向’a' p是‘a'的地址,p+1取到‘b'的地址 从’b‘的地址开始计算 5

	printf("%d\n", strlen(*p));//*p 取到 ’a'  ASICC值为 97 把97当做地址 传过去 非法 err报错

    printf("%d\n", strlen(p[0]));// p[0] 取到‘a' 97当做地址,非法 err 报错

	printf("%d\n", strlen(&p));// &p 取到的是指针变量p的地址,是一个二级指针 从指针变量p的地址开始计算 随机值

	printf("%d\n", strlen(&p + 1));//&p 取到的是指针变量p的地址,是一个二级指针 &p+1 取到p地址后面的地址开始计算,随机值

	printf("%d\n", strlen(&p[0] + 1));//  &p[0] 取到’a'的地址 让地址+1 取到‘b'的地址  从’b‘的地址开始计算 5

	return 0;
} 




//第四题

int main()
{
	//数组名只有两种情况下表示整个数组(sizeof(数组名) 和 &数组名 )
	//其它情况下都表示首元素的地址
	//而二维数组是由多个一维数组 组成的,二维数组的行数决定了一维数组的个数
	//因为二维数组是由多个一维数组 组成的
	//所以二维数组可以看成是一个一维数组,而这个一维数组的元素都是一个一维数组
	/*二维数组的数组名表示首元素地址,而这个首元素地址便是第一行的那个一维数组的地址
	所以二维数组的其他情况下的数组名取到的是第一个一维数组的地址
	在传参的时候传过去的是一个一维数组的地址,用数组指针来接收*/

	//二维数组
	int a[3][4] = { 0 };

	printf("%zd\n", sizeof(a));//48 sizeof(数组名) 表示整个数组,大小为:48 字节

	printf("%zd\n", sizeof(a[0][0]));//4 a[0][0] 等同于 *(*(a+0)+0) 取到的是第一行第一个元素 大小为:4 字节

	printf("%zd\n", sizeof(a[0]));//16 a[0]取到的是第一行的数据,也就是一维数组,a[0]其实就是第一行一维数组的数组名,
								//	而数组名单独出现sizeof(数组名) 表示整个数组,
								//  而第一行一维数组有4个元素,都是整型,大小为:16 字节

	printf("%zd\n", sizeof(a[0] + 1));//4/8 a[0]表示第一行一维数组的数组名,数组名表示首元素地址,
									// a[0]+1,表示第一行第二个元素的地址,本质上是地址,大小为:4/8 字节

	printf("%zd\n", sizeof(*(a[0] + 1)));//4 a[0]+1表示第一行第二个元素的地址 *(a[0]+1) 表示第一行第二个元素的内容,是一个整数,大小为:4 字节

	printf("%zd\n", sizeof(a + 1));//4/8 a表示二维数组首元素地址,a+1指向数组后面的一片空间,还是地址,大小为:4/8

	printf("%zd\n", sizeof(*(a + 1)));//16 *(a+1) 等同于 a[1] 是第二行一维数组的数组名,sizeof(数组名) 表示整个数组:大小为:16 字节

	printf("%zd\n", sizeof(&a[0] + 1));//4/8 a[0]表示第一行一维数组的数组名,
									 //&数组名,取到的是整个数组的地址,让整个数组的地址+1 指向第二行数组 还是地址:大小为:4/8
	
	printf("%zd\n", sizeof(*(&a[0] + 1)));//16  a[0]表示第一行一维数组的数组名 &a[0]取到的是第一行一维数组的地址,
										// &a[0]+1 表示第二行一维数组的地址,*(&a[0]+1)取到的是第二行二维数组
										// *(&a[0]+1) 等同于 a[1]

	printf("%zd\n", sizeof(*a));//16 a表示首元素地址,也就是第一行一维数组的地址,对其解引用操作取到第一行一维数组

	printf("%zd\n", sizeof(a[3]));//16 a[3]是第四行的一维数组的数组名,sizeof(数组名)表示整个数组的大小:16 字节
								// 但因为没有对空间进行访问 所以它只是一个地址编号,不会发生越界

	return 0;
}

指针变量笔试题

第一题:

#include 
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* ptr = (int*)(&arr+1);
	printf("%d %d\n", *(arr + 1), *(ptr - 1));

	return 0;
}

程序的运行结果是什么?

解析:

答案是:2  5

指针进阶(C语言)_第1张图片

 

1、arr数组名数字名表示首元素地址

     arr+1表示第二个元素的地址

    *(arr+1) 取到第二个元素的内容 2

2、&arr表示的是整个数组的地址,

     指针类型为:int (*)[5]一次可以访问5个整型的空间 

     让&arr+1跳过整个数组,让其指向数组后面的空间,它是数组指针

     再将其强制类型转换为(int*)的指针,一次只能访问4字节的空间

     此时再让ptr-1,取到的是数字5的地址,

     再对其解引用得到数字5

第二题:

#include 

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

解析:

答案是:  00 10 00 14

                00 10 00 01

                00 10 00 04

1、p是一个结构体类型的指针,让p+1是跳过整个结构体

      指针+1加的是其类型的大小,p是结构体类型,大小为20

      p+1起始就是让p的地址+20,,20转化成十六进制为14

      就是让0x100000加20,结果为 0x 00 10 00 14

2、将p强制类型转化成unsigned long 类型

      此时的p的内容被当成数值0x100000 

      让这个数值+1 得到另一个数值为:0x1000001

      %p打印的是数据原码的十六进制的形式

       因为正数的原码反码补码相同,

       所以结果依然是这个数值的十六进制形式

       而这个数值本来就是十六进制形式表示

       即结果为:0x 00 10 00 01

3、将结构体指针p强制类型转化为unsigned int* 类型

      让指针+1其实就是+上指针类型的大小

       此时p的类型为 unsigned int 类型,大小为:4字节

       让地址p+4 结果就是:0x 00 10 00 04

第三题:

#include 

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);

return 0;
}

解析:

答案为:4 , 2

指针进阶(C语言)_第2张图片

1、&a取到的是整个数组的地址

      &a+1指向的是数组后面的一片空间

      再让其强制类型转化成int*赋值给ptr

      此时ptr1也指向数组后面的一片空间

       ptr1能访问的空间大小为4字节

       ptr1[-1]等同于*(ptr1-1) 

       ptr1-1指向数组数值为4的位置

       再对其解引用取到指针指向的内容4

       用%x十六进制打印 十六进制的4 还是4

       所以结果为:4

指针进阶(C语言)_第3张图片

 2、a数组名表示首元素地址,将其强制类型转换int型

      将首元素地址,当成数值看待,让数值+1

      然后在将数值+1强制转化成int*类型,再赋值给ptr2

      此时的ptr指向的是首元素1的第二个字节的位置

      因为当前机器是小端存储,1在存储的时候是:01 00 00 00

      紧接着后续存放的是 02 00 00 00

      再对ptr2解引用,int类型的指针一次访问4个字节的空间

      访问到的内容是:00 00 00 02

       因为当前是小端机器,在存取的时候按照 02 00 00 00

       的方式存取,即*ptr2结果为:02 00 00 00

       用%x十六进制打印 十六进制的2000000 还是2000000

       所以结果为:2000000

第四题:

#include 
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);

return 0;
}

解析:

答案:1

指针进阶(C语言)_第4张图片

 

二维数组在赋值的时候,数组里面有三个逗号表达式

逗号表达式的结果是最后一个表达式的结果

即二维数组a里面存放的元素为:

{{1,3},{5,0},{0,0}}

此二维数组是三个一维数组 组成的

a[0]实际上是第一行一维数组的数组名

a[0]数组名表示第一行一维数组首元素地址

第一行一维数组的首元素为数字1,

a[0]就是数字1的地址,

将数字1的地址赋值给整型指针变量p

p[0] 等同于 *(p+0) 也就是*p

*p对指针解引用取到它所指向的内容 取到数字1

结果为:1

第五题:

#include 
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;
}

解析:

答案:FFFFFFFC  -4

指针进阶(C语言)_第5张图片

a是一个5行5列的数组

p是一个数组指针,指向的数组有4个元素 

数组名表示首元素地址

则a表示的是第一行的一维数组的地址

将一维数组的地址强制放进数组指针p里面

p指向的数组有4个元素,让p+1走的步长是4个整型的大小

由图可知,&p[4][2] - &a[4][2] 之间有4个元素 答案是-4

-4的原码:10000000 00000000 00000000 00000100

        反码:11111111 11111111 11111111 11111011

        补码:11111111 11111111 11111111 11111100

用十六进制:FF FF FF FC

则用%p打印:FFFFFFFC

用%d打印则是:-4

第六题:

#include 

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

解析:

答案: 10 ,  5

指针进阶(C语言)_第6张图片

 

1、aa是一个2行5列二维数组

     &aa取到的是整个数组的地址

     &aa+1 执行数组后面的空间

     将&aa+1强制类型转化成(int*)类型的指针

     并赋值给ptr1,此时ptr1也指向&aa+1的地址

     让*(ptr1-1 ) 步长为4个字节

     此时ptr1-1是数值10的地址

     *(ptr1-1) 取到的是数值10

2、*(aa+1) 等同于 aa[1] 

aa[1]其实就是第二行一维数组的数组名

数组名表示首元素地址也就是数字6的地址

将数字6的地址赋值给ptr2 ptr2的类型是int

ptr2-1取到数字5的地址

*(ptr2-1)对其解引用取到数值5

第七题:

#include 

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);

	return 0;
}

解析:

答案:at

指针进阶(C语言)_第7张图片

 

a数组名表示首元素地址

数组的首元素就是字符‘w’的地址

即a本质上是‘w’的地址的地址,其实是一个二级指针

将a赋值给pa此时pa也就是字符‘w’的地址的地址

让pa++,实际上是pa=pa+1,对指针+1的步长,是指针类型的大小

pa指向了第二个元素的地址,而数组的第二个元素为:‘a’的地址

*pa 再对pa解引用取到‘a’的地址

用%s打印,从‘a’的地址开始打印字符直到碰到\0结束

第八题:

#include 

int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);

return 0;
}

解析:

答案:POINT

           ER

           ST

           EW

指针进阶(C语言)_第8张图片

c数组里面存放四个元素,每个元素都是char*类型也就是字符指针

cp数组里面存放四个元素,每个元素都是char**类型,也就是二级指针

通俗讲:就是存放字符的地址的地址

cpp是一个三级指针变量

里面存放的是cp的首元素的地址

而cp的首元素是字符地址的地址是一个二级指针

1、(**++cpp)

      ++cpp取到的是cp数组第二个元素的地址

     先对cpp解引用取到cp数组第二个元素的内容

     内容是一个地址,而这个地址是字符地址的地址

     再对其进行解引用取到‘P’的地址

     用%s打印从‘p’地址开始打印到\0结束

     结果为:POINT

2、(*-- * ++cpp + 3)

      先++cpp 取到cp数组第三个元素的地址

      再*++cpp 解引用取到第三个元素的内容

      内容是字符'N'的地址的地址

      再进行--操作就是变成字符‘E’的地址的地址

      再进行* 解引用操作取到字符‘E’的地址

      再+3让字符‘E’的地址+3取到下一个字符‘E’的地址

      用%s打印从‘E’地址开始打印到\0结束

      结果为:ER

3、( *(cpp[-2] + 3))

      cpp[-2]等同于*(cpp-2) 

      cpp-2取到的是cp数组首元素的地址

      再对其解引用,取到的是字符‘F’的地址的地址

      在进行解引用取到的是字符‘F’的地址

      再让其+3 取到的是字符‘S’的地址

      用%s打印从‘S’的地址开始打印遇到\0结束

      结果为:ST

4、

      cpp[-1][-1] 等同于 *(*(cpp-1)-1)

      cpp-1取到cp数组第二个元素的地址

      再对其*解引用操作 取到字符‘P’的地址的地址

      再让地址-1取到字符‘N’的地址的地址

      再对其*解引用取到字符‘N’的地址

      再让其+1取到字符‘E’的地址

      用%s打印从‘E’的地址开始打印遇到\0结束

      结果为:EW

你可能感兴趣的:(c语言,c++,开发语言)