【C语言进阶】 指针1

指针进阶

回顾

内存会划分为一个个的内存单元,每个内存单元都有一个独立的编号,而编号也称为地址。地址在C语言中也被称为指针。

指针(地址)需要存储起来——存储到变量中,这个变量就被称为指针变量。

int a = 10; int* pa = &a;

指针(地址)的大小是固定的4/8个字节(32位平台/64位平台)地址是物理的电线上产生

32位机器-32根地址线-1/0 32个0/1组成的二进制序列,把这个

二进制序列就作为地址,32个bit位才能存储这个地址,也就是需要4个字节才能存储,所以指针变量的大小就是4个字节

同理64位机器上,地址的大小是64个0/1组成的二进制序列,需要64个bit位存储,也就是8个字节。所以指针变量的大小是8个字节。

1.字符指针

int main()
{
	char ch = 'w';
	//如果我们要将ch的值修改,那么可以怎么做呢?
	//法一
	ch = 'a';

	//法二
	char* pc = &ch;
	*pc = 'a';

	return 0;
}
int main()
{
	const char* pstr = "hello world.";//这里是把一个字符串放到pstr指针变量里了吗?
	printf("%s\n", pstr);
	return 0;
}

值得注意,我们很容易误以为以上操作是将hello world存放到字符指针pstr中,实际上是将hello world首字符的地址放到了pstr中。

int main()
{
	char arr[] = "abcdef";
	const char* p = "abcdef";
	printf("%s\n", p);
	printf("%c\n", *p);
	return 0;
}

可以看出,指针存的是字符串的首字母。
之所以能打印出一行字符串,是printf的作用,找到了首字符,并以此地址往后寻找并打印。

const放在*左边,修饰的*p,放在*右边,只限制p

前者限制内容,后者限制地址。

image-20230730053522481

例题

下列最终输出的是什么?

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";

	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";

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

输出结果:

image-20230730053511740

解析

image-20230730053456176

2.指针数组

就是用来存放指针的数组。

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

使用指针数组模拟实现二维数组(虽然没多大实际用处)

int main()
{
	int arr1[] = { 1,2,3,4,5 };//arr1 - int*
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	//指针数组
	int* arr[3] = { arr1, arr2, arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

3.数组指针

类比一下:

整型指针-指向整型变量的指针,存放整型变量的地址的指针变量字符指针-指向字符变量的指针,存放字符变量的地址的指针变量

那么,
数组指针-指向数组的指针,存放的是数组的地址的指针变量

3.1 指针数组与数组指针的区别

int* p1[10];//指针数组

p1先跟[10]结合说明是数组([]的优先级较*更大),而int*表明数组里的每个元素是指向整型的指针,p1是数组名。

所以是指针数组

int(*p2)[10];//数组指针

*p2在括号内,所以优先看这个,为指针。
剩下的便是int [10],是一个整型数组,
所以是一个名为p2的指针,指向一个整型数组,共有10个单位

所以是数组指针


3.2 &数组名VS数组名

数组名

总结

数组名是首元素的地址。

有2个例外

  1. sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节。
  2. &数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址。

除此之外,所有的地方的数组名都是数组首元素的地址

以下是两个例外的案例。

//数组名是首元素的地址。
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	return 0;
}

结果如下:image-20230730053113716

//`sizeof(数组名)`,这里的数组名不是数组首元素的地址,数组名表示整个数组,`sizeof(数组名)`计算的是整个数组的大小,单位是字节。
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	
	printf("%d\n", sizeof(arr));
	//如果是此处的arr是首字符的话
	//那么结果将会是4/8
	return 0;
}

结果如下:image-20230730053058514

&数组名VS数组名

请看这段代码:

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);
	
	return 0;
}

image-20230730053048400

根据结果,我们会以为&数组名VS数组名打印的地址是一样的,但确实如此吗?

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr+1);

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);
	
	printf("%p\n", &arr + 1);
	printf("%p\n", &arr + 1);

	return 0;
}

结果如下:image-20230730053036210

分析:image-20230730052401121

arr&arr[0]都是指首元素的地址
&数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址。

3.3 数组指针的使用

先提醒一下,一般不是这么使用的。

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	//数组的地址,存储到数组指针变量
	//int[10] *p = &arr;//err
	int (* p)[10] = &arr;//int(*)[10]
	//指针,指向一个10个单位的数组,每个单位存放一个整型
	int* p2 = &arr;//err,该类型为存放整型指针

	return 0;
}

如何打印?

//咱们一开始接触指针,会这么写
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}
//使用数组指针呢?
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;//咱们需要的是一整个数组的地址,而不单单是首字符的地址。
	//arr-->err
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *((*p) + i));
		//*p-->*&arr-->arr
        //除开*P,其他的格式:*(__+i)和第一个例子一样。
	}
	return 0;
}

数组指针怎么使用呢?一般在二维数组上才方便

二维数组的每一行可以理解为二维数组的一个元素,每一行又是一个一维数组
所以呢?二维数组其实是一维数组的数组

image-20230730052347533

二维数组的数组名,也是数组名,数组名就是数组首元素的地址。

arr - 首元素的地址
    - 第一行的地址
    - 一维数组的地址 - 一个数组的地址!!!
没有使用数组指针
//二维数组传参,形参是二维数组的形式
void Print(int arr[3][5], int r ,int c)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
	Print(arr, 3, 5);
	return 0;
}

使用了数组指针

void Print(int(*p)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
	Print(arr, 3, 5);
	return 0;
}

4.数组参数和指针参数

4.1 一维数组传参

一维数组传参,形参的部分可以是数组,也可以是指针

示例

void test1(int arr[5], int sz)
{}
void test2(int* p, int sz)
{}

int main()
{
	int arr[5] = { 0 };
	test1(arr, 5);
	test2(arr, 5);
	return 0;
}

重难点判断题

#include <stdio.h>
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 main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };//指针数组
	test(arr);
	test2(arr2);
}

4.2 二维数组传参

一维数组传参,形参的部分可以是数组,也可以是指针

示例

void test3(char arr[3][5], int r, int c)
{}

void test4(char (*p)[5], int r, int c)
{}
int main()
{
	char arr[3][5] = {0};
	test3(arr, 3, 5);
	test4(arr, 3, 5);

	return 0;
}

重难点判断题

void test(int arr[3][5])//ok?
{}
//正确
void test(int arr[][])//ok?
{}
//错误
//形参部分,行可以省略,但是列不能省略
void test(int arr[][5])//ok?
{}
//正确

void test(int* arr)//ok?
{}
//错误
void test(int* arr[5])//ok?
{}
//错误
//形参是:指针数组
void test(int(*arr)[5])//ok?
{}
//正确
void test(int** arr)//ok?
{}
//错误
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
    //当你尝试将 arr 传递给 test 函数时,arr 是一个指向整数数组的指针
    //数组名是首元素,一行的地址
    //类型是 int (*)[5],表示一个指向包含5个整数的数组的指针。
}

总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
因为对一个二维数组,可以 不知道有多少行,但是必须知道一行多少元素。
这样才方便运算。

一行有几个元素很重要,但是行不重要
下一行从哪个元素开始,必须由一行有几个元素来决定

4.3 一级指针传参

示例

#include<stdio.h>
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]);

	print(p, sz);
	//一级指针p;传给函数
	return 0;
}

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test(char* p)
{}

char ch = '2';
char* ptr = &ch;
char arr[] = "abcdef";

test(&ch);
test(ptr);
test(arr);

4.4 二级指针传参

示例

#include<stdio.h>
void test(int** ptr)
{
	printf("num=%d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
	return 0;
}

当函数的参数为二级指针的时候,可以接受什么参数?

void test(char** p)
{}

char n = 'a';
char* p = &n;
char** pp = &p;
char* arr[5];

test(&p);
test(pp);
test(arr);

5.函数指针

函数指针 - 指向函数的指针

示例1

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

int main()
{
	int arr[10] = { 0 };
	int(*pa)[10] = &arr;
	/*printf("%p\n", &Add);
	printf("%p\n", Add);*/

	int(*pf)(int, int) = &Add;
	//pf是函数指针变量(名)
	//int(*)(int,int)是函数指针类型
	return 0;
}

image-20230730052328888

函数名是函数的地址,
&函数名也是函数的地址

示例2

void test(char* pc,int arr[])
{}
int main()
{
	void (*pf)(char*, int[10]) = test;

	return 0;
}

示例3

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

int main()
{
	int(*pf)(int, int) = Add;

	int r = Add(3, 5);
	printf("%d\n", r);

	int m = (*pf)(4, 5);
	printf("%d\n", m);

	return 0;
}

示例4——很容易搞错的咧

void test()
{
 printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void


有难度的来了,两道题,判断一下是什么意思吧。

选自《C陷阱和缺陷》

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

代码1的思路过程

//代码1
(*(void (*)())0)();

先从数字0下手,0可以是整型(int),也可以是指针(地址)(int*)

0x0012ff40 既可以是int,也可以是int*

void(*p)()指的是p是一个函数指针,指针变量p指向的函数是一个没有参数,并且返回类型为void(即无返回值)的函数。

void (*)()是函数指针类型。

(void (*)())0:就是把0强制类型转换为一个函数指针

(*(void (*)())0):这是对上述函数指针的解引用操作,意味着从地址 0 中取出一个函数指针,然后调用该指针指向的函数。

(*(void (*)())0)():就是调用0地址处的这个函数

代码2的思路过程

//代码2
void (*signal(int , void(*)(int)))(int);

先论述如何使用typedef简化这一串代码

typedef unsigned int uint;
typedef int* prt_t;

unsigned int重定义为uint,起到简写的作用。

注意,定义数组指针和函数指针就有点特殊了

typedef int(*p)[10] parr_t//->err
typedef int*parr_t)[10];//->right!

typedef int (*pf_t)(int,int);  

int main()
{
    uint u1;
    prt_t p1;
    int* p2;
    return 0;
}

解析

//代码2
void (*signal(int , void(*)(int)))(int);
int main()
{
	//signal 是一个函数声明
	//signal 函数有2个参数,第一个参数的类型是int,第二个参数的类型是 void(*)(int) 函数指针类型
	//该函数指针指向的函数有一个int类型的参数,返回类型是void
	//signal 函数的返回类型也是void(*)(int) 函数指针类型,该函数指针指向的函数有一个int类型的参数,返回类型是void

	typedef void(*pf_t)(int);
	pf_t signal(int, pf_t);

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

	return 0;
}
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值