C语言指针(2)

C语言指针(2)

距离上次的博客已有一段时间,让我们接下来再次走进指针的世界,更加彻底的了解指针.

1.字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*,使用方式一般是下图为例:

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
    //将字符放入字符指针,使用字符指针修改ch的值。
}

//字符串存放到字符指针中,字符指针存放的是首元素的地址,数组arr中存放的也是首元素地址,pt和arr是等价的。%c打印一个字符,%s打印是从首地址开始一直到\0结束。
	char* pt = "hello world";
	char arr[] = "hello world";
	printf("%s\n", pt);
	printf("%s\n", arr);

在这里插入图片描述

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

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

2. 指针数组

 指针数组是一个存放指针的数组
在这里插入图片描述
在这里插入图片描述

3.数组指针

 上面讲到过指针数组是一个数组。
 数组指针是指针,本质都是看最后的词语,前面是修饰词。
 之前也学习过其他的指针:
 整形指针: int * pint; 能够指向整形数据的指针。
 浮点型指针: float * pf; 能够指向浮点型数据的指针。
 那数组指针应该是:能够指向数组的指针。

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

3.1 &(取地址)数组名和数组名

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

上述代码&arr和arr打印的值是一样的,都是指向首元素的地址。
在这里插入图片描述
虽然打印的值是一样的,但是并不代表&arr和arr就是一样的,如下图代码

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

在这里插入图片描述根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义是不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

之前学过:只有sizeof(数组名)和&数组名是取的整个数组的大小。

3.2数组指针的使用

数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //但是我们一般很少这样写代码。
    int i;
    for (i = 0; i < 5; i++) {
		printf("%d\n", (*p)[i]);
	}//*p和arr是等价的,使用arr即可打印数组。
    return 0;
}

//二维数组的传参方式
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
    int i = 0;
    for(i=0; i<row; i++)
   {
        for(j=0; j<col; j++)
       {
            printf("%d ", arr[i][j]);
       }
  printf("\n");
   }
}
//第二种写法,arr是一个指针,指向一个数组,数组有5个元素,每个元素类型是int

void print_arr2(int (*arr)[5], int row, int col)
{
    int i = 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};
   	print_arr1(arr, 3, 5);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行,是一个一维数组
    
    //所以这里传递的arr,其实相当于二维数组第一行的地址,是一维数组的地址,可以用数组指针来接收
    print_arr2(arr, 3, 5);
    return 0;
}

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5];  //整形数组
int *parr1[10];  //指针数组,数组里有10个元素,每个元素类型是int*
int (*parr2)[10];  //parr2是一个指针,指向一个数组,数组里有10个元素,每个元素类型是int
int (*parr3[10])[5];  //去掉数组名和方括号,剩下的就是类型。int (*)[5],是一个数组指针类型。
//数组里存储10个元素,每个元素类型是数组指针。数组指针指向1个数组,数组有5个元素,每个元素类型是int

4.数组传参

#include <stdio.h>
void test(int arr[])  //可以,数组传参,数组接收
{}
void test(int arr[10])  //可以,本质上实参传进来的首元素地址,形参可以写成数组的形式,[]可以写也可以不写,并不会创建一个有10个元素的数组。
{}
void test(int *arr)  //可以,数组名等于首元素地址,使用地址接没有问题
{}
void test2(int *arr[20]) //可以,数组传参,数组接收
{}
void test2(int **arr)  //可以,arr2是指针数组,数组名是首元素地址,首元素类型是int*,int*的地址是int**.
{}
int main()
{
	 int arr[10] = {0};
	 int *arr2[20] = {0};
	 test(arr);
	 test2(arr2);
}

4.1二维数组传参

void test(int arr[3][5]) //可以
{}
void test(int arr[][])  //不可以,二维数组传参,行可以省略,但是列不能省略。
{}
void test(int arr[][5])//可以
{}
//总结:二维数组传参,行可以省略,但是列不能省略。,函数形参的设计只能省略第一个[]的数字。
//对一个二维数组,可以不知道有多少行,但是必须知道一列有多少元素。

void test(int *arr)  //不可以
{}
void test(int* arr[5])  //不可以,这是指针数组,不是指针
{}
void test(int (*arr)[5])  //可以,二维数组首元素还是一个数组,使用数组指针存放地址。
{}
void test(int **arr)  //不可以
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

4.2 一级指针传参

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

void test(char* p) {
	*p = 'a';

}

char a = 'w';
char* p = &a;
char arr[10]={0};
//test(&a); //函数参数是一级指针时,可以传变量的地址,可以传一维数组的数组名,也可以使用指针传参.
test(p);
test(arr);
printf("%c\n", a);

在这里插入图片描述

4.3 二级指针传参

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

void test(char** p)
{

}
int main() {
	char c = 'b';
	char* pc = &c;
	char** ppc = &pc;
	char* arr[10];
	test(&pc);//传一级指针的地址
	test(ppc);//二级指针本身
	test(arr);
	//指针数组,每个元素类型是int*,数组名是首元素地址,int*的地址可以使用int**接收
	
}

5.函数指针

 我们已经学习了数组指针,整形指针,字符指针等一系列的指针,函数指针顾名思义就是存放函数的指针,在了解函数指针之前,我们先来看看下面的代码。

void test()
{
 printf("hehe\n");
}
int main()
{
	 printf("%p\n", test);
	 printf("%p\n", &test);
 return 0;
}

在这里插入图片描述 上图代码输出的两个地址都是一样的值,这两个地址都是 test 函数的地址,可以认为&函数名和函数名是一样的
 如果想要把函数的地址存放起来,应该怎么样保存呢?
 这时候就要用到函数指针了,之前已经有了写数组指针的例子,
 先来回顾一下数组指针:int (*p)[10]=&arr ;
 因为加了括号,星号 * 先和p结合,说明p是一个指针变量,[10]表示p指向的是一个数组,数组里面有10个元素,前面的int表示数组类型是int.
 函数指针也可以照猫画虎,先来看下图的代码:

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

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

5.1 函数指针的使用

int add(int x, int y) {
	return x + y;

}
int main() {
	int (*pt)(int, int) = add; //add===pt
	printf("%d\n", pt(1, 2));
	printf("%d\n", (*pt)(1, 2)); //*不需要也可以
	printf("%d\n", add(1, 2));
}

在这里插入图片描述

5.2 有意思的代码

先来阅读下面两段代码:

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

这两段代码看到后,,一脸懵逼…
静下心来,慢慢分析分析

第一个代码:
1.void(*)() //是一个函数指针类型
2.(void(*)())0 //把0强制类型转换成函数指针类型,被解释成一个函数地址
3.*(void(*)()))0  //对0处的地址进行解引用操作
4.*(void(*)()))0() //调用0地址处的函数


第二个代码:
1.signal()  //signal先和()结合,说明signal是一个函数
2.signal(int , void(*)(int)) //signal函数第一个参数类型是int,第二个参数类型是函数指针类型,该函数指针指向一个参数为int,返回类型为void的函数。
3.void(*)(int)  //剩下的是函数返回值类型,返回值类型是函数指针类型,该函数指针指向一个参数为int,返回类型为void的函数,signal是一个函数声明.


void(*)(int)signal(int , void(*)(int)));//写成这样子更好理解,但是C语言不允许。

可以使用typedef来简化代码

typedef void(*pfun_t)(int); //对void(*)(int)的函数指针类型重命名为pfun_t


pfun_tsignal(int , pfun_t); //简化后的样子

6. 函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组

int *arr[10];  //整形指针数组
int (*parr1[10])();
parr1 先和 [] 结合,说明 parr1是数组,去掉数组名和[],剩下的就是数组类型
是 int (*)() 类型的函数指针

6.1 函数指针数组的用途

下面要实现一个计算器:

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() {
	//计算器
	/*int input, ret;
	int a, b;
	do {
		menu();
		printf("请输入菜单键:\n");
		scanf("%d", &input);
		if (input >= 1 && input <= 4) {
			printf("请输入两个操作数:\n");
			scanf("%d %d", &a, &b);
			int(*pArr[5])(int, int) = { NULL,add,sub,mul,div }; //使用函数指针数组存放各个函数地址
			ret = (pArr[input])(a, b);  
			printf("%d\n", ret);
		}
		else if (input == 0) {
			printf("退出程序\n");
			break;
		}
		else {
			printf("请重新输入\n");
		}

	} while (input);

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

 函数指针:int(*p)(int,int);
 函数指针数组:int(*p2[10])(int , int);
 指向函数指针数组的指针: int( *(p3)[4])(int,int)=&p2;
 p3先和
结合,说明是一个指针,[4]说明p指向数组,里面有4个元素,每个元素类型是函数指针类型

8.回调函数

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

int Cacl(int (*pt)(int, int)) {
	int a, b, ret;
	printf("请输入两个操作数:\n");
	scanf("%d %d", &a, &b);
	ret = pt(a, b);
	printf("%d\n", ret);

	do {
		menu();
		printf("请输入菜单键:\n");
		scanf("%d", &input);
		switch (input) {
		case 1:
			Cacl(add);   //调用add函数时,把add函数地址传入Cacl(),pt指针存放add地址,调用了add函数。
			break;
		case 2:
			Cacl(sub);
			break;
		case 3:
			Cacl(mul);
			break;
		case 4:
			Cacl(div);
			break;
		case 0:
			printf("退出程序\n");
			break;

		default:
			printf("请重新输入\n");
			break;
		}

	} while (input);
}

8.1 qsort函数解析与实现

void qsort(void* base,size_t num,size_t size, int(*compar)(const void *,const void*));

4个参数解析:
void* base:  base中存放的是待排序数据中第一个对象的地址
size_t num: 排序数据元素的个数
size_t size: 排序数据中一个元素的大小,单位是字节
int(*compar)(const void  *e1,const void*  e2): 是用来比较待排序数据中2个元素的函数
e1和e2是两个指针,实参要传入两个地址,不同类型排序时要把e1和e2强制转换成其他类型。
比如要比较两个数字的大小,需要把e1和e2强制转换成int*
 
8.1.1 qsort实现冒泡排序和结构体排序
void print(int arr[] , int sz) {
	int i;
	for (i = 0; i < sz; i++) {
		printf("%d\n", arr[i]);
	}

}
int cmp_int(const void* e1, const void* e2) {
	return *((int*)e1) - *((int*)e2);    //强制类型转换int* 
}
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, sz);

这里的冒泡排序是升序排序,如果想要降序排序,把com函数里e2和e1换一下就可以。
return ((int)e2) - ((int)e1);

//这里采用年龄排序
struct stu {
	int age;
	char name[20];
};
void print(struct stu *s , int sz) {
	int i;
	for (i = 0; i < sz; i++) {
		printf("%d %s\n", (s+i)->age,s[i].name);
	}
}
int cmp_age(const void* e1, const void* e2) {
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}

	struct stu s[] = { {18,"lisi"},{12,"wangwu"},{17,"zhaoliu"} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_age);
	print(s, sz);

在这里插入图片描述

8.2 qsort实现通用冒泡排序

void bubble_sort(void* base,int sz,int width,int(*cmp)(const void * e1,const void* e2)){
	int i,j;
	for (i = 0; i < sz-1; i++) {
		for (j = 0; j < sz - 1 - i; j++) {
			if (cmp((char*)base+(j*width),(char*)base+(j+1)*width) > 0) {
				swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}
void swap(char* e1, char* e2,int width) {
	int i = 0;
	for (i = 0; i < width; i++) {
		char tmp = *e1;
		*e1 = *e2;
		*e2 = tmp;
		e1++;
		e2++;
	}
}
void print(int arr[], int sz) {
	int i;
	for (i = 0; i < sz; i++) {
		printf("%d\n", arr[i]);
	}
}
int cmp_int(const void* e1, const void* e2) {
	return *((int*)e1) - *((int*)e2);
}
int main(){
	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, sz);
}

void* 存放任意指针类型变量,void*就是一个大箱子,啥指针类型都能往里放。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值