【Amazing! C】指针进阶(二)

目录

一、字符指针

二、指针数组

三、数组指针

四、数组参数、指针参数

4.1 一维数组传参

4.2 二维数组传参

4.3 一级指针传参

4.4 二级指针传参

五、函数指针


一、字符指针

        字符指针是指针,是指向字符的指针,字符指针的类型为char*

例1:

char ch = 'w';
char* pc = &ch;//pc就是字符指针,存放ch的地址,也可以通过解引用改变ch的值

例2:

char* p = "abcdef";//p是字符指针,把首字符a的地址存放在p种
//其中,"abcdef"是常量字符串,存放在常量区,所以只能读取,不能修改
//其实质应该是
const char* p = "abcdef";//避免*p来修改其中的内容

*p = 'w';//err

        值得注意的是,此处并不是把“abcdef”存放在p中,因为“abcdef”的大小是6个字节,地址的大小是4个字节。

         经过上述理解,我们来看这道题目:

#include<stdio.h>

int main()
{
    char str1[] = "hello world.";
    char str2[] = "hello world.";

    char * str3[] = "hello world.";
    char * str4[] = "hello world.";

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

    if (&str3 == &str4)
        printf("&str3 and &str4 are same\n");
    else
        printf("&str3 and &str4 are not same\n");

    return 0;
}

 运行结果如下:

究其原因:

  • str1和str2是字符数组,分别在内存中创建了两个独立的空间,空间中都存放“hello world.”,所以地址是不一样的。
  • str3和str4是字符指针,“hello world.”是常量字符串,字符指针指向常量字符串,常量字符串储存在只读区域,一旦创建好就不能被修改,那么程序就没有必要保存多份。所以,虽然分别创建了字符指针str3和str4,但都指向相同的地址。

二、指针数组

  • 整形数组--存放整形的数组
  • 字符数组--存放字符的数组
  • 指针数组--存放指针的数组

        指针数组是数组,存放在数组中的元素都是指针类型的。例如:int* arr[5];存放整型指针的数组。

        用法:可以使用指针数组描述(模拟)一个二维数组。

#include<stdio.h>

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

	int* arr[] = { 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;
}
#include<stdio.h>

int main()
{
	char* arr[5] = { "hello world","hehe","C","amazing","C++" };//产生5个首字符的地址
	//char表示每个元素指向的类型是char,*代表arr是地址
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}

	return 0;
}

三、数组指针

  • 数组指针 - 是指针,指向数组的指针
  • 字符指针 - 指向字符的指针
  • 整型指针 - 指向整型的指针
  • 浮点型的指针 - 指向浮点型的指针

        数组指针和指针数组很容易混淆。为了理解记忆,我们看arr先和谁结合。arr先与*结合为*arr,说明是指针;arr先与[ ]结合为arr[ ],说明是数组。

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

#include<stdio.h>

int main()
{
    int arr[10] = { 0 };
    p = &arr;//p是用来存放数组的地址,p是数组指针
    //我们看这样写 int * p[10];//这是一个指针数组,一共10个元素,每个元素是int类型
    //既要有数组的元素,也要有指针的元素
    //所以,我们希望p首先是指针,不能和[]结合
    //那么,形如int * p,这个*代表指针。那么我们也在p旁边安插*
    //int (*p)[10] = &arr;//先看到*p,说明p是指针,向后看[10]表示指向的是数组的,数组的每个元素的类型是int

    char* arr2[5];//指针数组
    char*(*pc)[5] = &arr2;//首先是数组指针,所以(*pc),指向的是数组,共5个元素,所以写[5],数组的每个元素是char*

    int arr3[] = { 1,2,3 };
    int(*p3)[] = &arr3;//err   int(*)[0]和int(*)[3]数组的下标不同,所以此处不写大小时,会默认是0
    int(*p3)[3] = &arr3;

    return 0;
}

        数组指针到底有什么用呢?我们通过代码进行了解。

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;//这样写,确实把数组地址放在p中去了,但是拿p去访问数组中的元素其实是非常不方便的。

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", (*p)[i]);//p中放的是&arr,取地址&和解引用*是可以相互抵消的,所以解引用p可以得到数组名(*&arr)
		printf("%d ", p[i]);//err,p[i]等价于*(p+i),而p中存放的是&arr,所以每次+i都会跳过整个数组的长度
		//arr等价于p
	}

	return 0;
}

        所以在一维数组中不适用数组指针,往往在二维数组中使用,例如:

#include<stdio.h>

void print(int arr[3][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 ", 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;
}

        我们知道,数组名是首元素的地址。所以,上述代码可修改为:

#include<stdio.h>

void print(int (*arr)[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 ", 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);//arr是二维数组的数组名,是首元素的地址,首元素的地址就是第一行的地址
    //第一行的地址就是一维数组的地址,那么上边就需要一个指针,指向一维数组来接收

	return 0;
}
#include<stdio.h>

void print(int arr[], int sz)//即使写成数组的形式,也不会真实的创建一个数组,所以这里arr[]方括号中可以省略,但二维数组中,只能省略第一个方括号中的数组,即行可以省略,列不可以
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void print(int *arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);//arr[i]等价于*(arr+i)
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);

	return 0;
}
int arr[5];//arr是一个能够存放5个整型数据的数组
int* parr1[10];//parr1是一个数组,数组10个元素,每个元素的类型是int*
int(*parr2)[10];//parr2是指针,该指针是指向数组的,指向的数组有10个元素,每个元素的类型是int
int(*parr3[10])[5];//parr3是一个数组,是存放数组指针的数组,数组有10个元素,存放的这个数组指针,指向的数组有5个元素,每个元素的类型是int

四、数组参数、指针参数

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

4.1 一维数组传参

        数组传参,形参是可以写成数组形式的;数组传参的本质是传递数组首元素的地址,所以,数组传参,形参也可以是指针

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//我们说,数组名是首元素的地址,数组中每个元素都是int*类型。
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

4.2 二维数组传参

void test(int arr[3][5])//ok
{}
void test(int arr[][])//no//行可以省略,列不能
{}
void test(int arr[][5])//ok
{}
void test(int* arr)//no
{}
void test(int* arr[5])//no
{}
void test(int(*arr)[5])//ok
{}
void test(int** arr)//no
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

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]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

        那么基于上述代码,我们提出思考,当一个函数的参数部分为一级指针时,函数能接收什么参数?

void test(int*p)
{}

int a = 10;
int* ptr = &a;
int arr[5];

test(&a);//传整型一维数组的数组名
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(int ** p)
{}

int n = 10;
int* p = &p;
int** pp = &p;
int* arr[6];
test(&p);
test(pp);
test(arr);

五、函数指针

        数组指针 - 指向数组的指针 - 存放的是数组的地址 - &数组名 就是数组的地址
        函数指针 - 指向函数的指针 - 存放的是函数的地址 - 怎么得到函数的地址呢?是不是&函数名?

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", &Add);//005D13B6
	//说明,&函数名 就是函数的地址
	printf("%p\n", Add);//005D13B6
	//函数名也是函数的地址,二者没有区别

	//int(*pf1)(int, int) = Add;//pf1就是函数指针变量
	int(*pf2)(int, int) = &Add;//明白说明pf2就是一个指针,向后看到圆括号,说明是函数,函数的参数类型是int,返回类型是第一个int
	//写法与数组指针非常类似
	//int ret = (*pf2)(2, 3);//解引用,这样的写法是对应习惯
	//printf("%d\n", ret);

	//我们知道
	//int ret = Add(2, 3);//既然Add是函数的地址,同时pf2也是函数的地址,那么如下形式:
	int ret = pf2(2, 3);
	printf("%d\n", ret);
	//由此可以看出,圆括号中的*其实是没有起作用的

	return 0;
}

         接下来,我们看两个比较有意思的代码,来对函数指针做进一步了解。

#include<stdio.h>

int main()
{
	(*(void(*)())0)();
	//从0开始看,0是整数类型,0之前,看(*),如果是(*p),就是p是指针,指向函数,返回类型是void,p去掉
	//void(*)()是一个函数指针类型,类型放在括号中,想要强制类型转化
	//强制类型转化后,再解引用,即为调用函数,调用函数括号中没有参数,即没有传参
	//综上,是调用0地址处的函数,这个函数没有参数,返回类型是void
	return 0;
}
#include<stdio.h>

int main()
{
	void (*signal(int, void(*)(int)))(int);
	//先从signal下手,signal是函数名,后边圆括号是函数,两个参数一个是整型类型,另一个是函数指针类型
	//除上述以外,剩下的也是函数指针类型,void(*)(int)
	//综上,这个代码是一次函数声明,声明的是signal函数,signal函数的参数有2个,第一个是int,第二个是void(*)(int),该函数指针指向的函数,参数是int,返回类型是void
	//signal函数返回类型也是函数指针类型,该类型是void(*)(int),该函数指针指向的函数,参数是int,返回类型是void
	//也可改写
	//typedef void(*pfun_t)(int);//类型重命名typedef void(*)(int)命名为pfun_t,但是pfun_t按照规范要求必须放在*旁边
	//pfun_t signal(int, pfun_t);
	
	return 0;
}

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值