探索指针(3)-C语言

目录

1.字符指针变量

[一. 什么是字符指针?](#一. 什么是字符指针?)

二.字符指针的使用

三.常量字符串与字符指针的关系

四.字符数组和字符串常量

2.数组指针变量

[一. 指向数组的指针](#一. 指向数组的指针)

[3. 二维数组传参的本质](#3. 二维数组传参的本质)

指针形式的函数参数

4.函数指针变量

[一. 函数指针的声明](#一. 函数指针的声明)

[二. 初始化和使用函数指针](#二. 初始化和使用函数指针)

三.typedef关键字的使用

5.函数指针数组

一.函数指针数组的声明

二.函数指针的使用

6.转移表

一.基本概念

二.转移表实际应用


1.字符指针变量

一. 什么是字符指针?

字符指针(char *)是一种指向字符类型数据的指针。

二.字符指针的使用

代码示例:

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

	printf("%c", a);

	return 0;
}

代码输出 :

a

三.常量字符串与字符指针的关系

常量字符串存储位置:

常量字符串存储在只读数据区。这意味着你不能修改这些字符串的内容,否则会导致未定义行为,通常是运行时错误(例如,段错误)。

什么是常量字符串?

常量字符串是用双引号括起来的字符序列,例如 "Hello, World!"。在C语言中,常量字符串是隐式的以空字符 \0 结尾的字符数组。

代码示例:

#include <stdio.h>

int main() {
    const char *str = "Hello, World!";
    printf("%s\n", str);

    // 尝试修改常量字符串(这是不允许的,会导致运行时错误)
    // str[0] = 'h'; // 运行时错误:段错误

    return 0;
}

在这个示例中,字符串 "Hello, World!" 存储在只读数据区,指针 str 指向这个只读数据区的地址。如果尝试修改字符串内容,程序会崩溃。

四.字符数组和字符串常量

需要区分的是,字符数组和常量字符串的存储位置和可修改性是不同的。

字符数组:

#include <stdio.h>

int main() {
    char str[] = "Hello, World!";
    printf("%s\n", str);

    // 修改字符数组的内容
    str[0] = 'h';
    printf("%s\n", str);

    return 0;

在这个示例中,str 是一个字符数组,存储在栈区或数据区,可以修改其内容。

2.数组指针变量

引言:

整型指针->存储整型的地址 字符指针->存储字符的地址

那么自然我们可以知道 数组指针->存储数组的地址

一. 指向数组的指针

数组指针变量是一个指针,指向一个数组的首地址。数组指针变量可以通过不同的方式来声明和使用。

声明和初始化

指向数组的指针的声明形式如下:

type (*pointer_name)[size];

其中,type是数组元素的类型,size是数组的大小。

例如,声明一个指向长度为5的int数组的指针:

int (*ptr)[5];

初始化和使用

可以使用一个实际的数组来初始化数组指针变量:

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int (*ptr)[5] = &arr;  // 指针ptr指向数组arr

    // 访问数组元素
    printf("Element 0: %d\n", (*ptr)[0]);
    printf("Element 1: %d\n", (*ptr)[1]);
    printf("Element 2: %d\n", (*ptr)[2]);
    printf("Element 3: %d\n", (*ptr)[3]);
    printf("Element 4: %d\n", (*ptr)[4]);

    return 0;
}

代码输出:

Element 0: 1

Element 1: 2

Element 2: 3

Element 3: 4

Element 4: 5

在这个示例中,ptr是一个指向长度为5的int数组的指针,通过*ptr可以解引用指针并访问数组元素。

3. 二维数组传参的本质

当你将一个二维数组传递给一个函数时,本质上是将一个指向数组首元素的指针传递给函数。

那么这里我们会提出一个疑问,二维数组的首元素是什么?是arr[0] [0]吗?

二维数组的声明和内存布局

示例声明

int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };

在内存中,二维数组arr的布局是连续的,存储顺序如下:

| 1 | 2 | 3 | 4 | 5 | 6 |

二维数组的首元素

二维数组的首元素是第一个一维数组的首元素。在上面的例子中,二维数组arr的首元素是arr[0],也就是一个一维数组{1, 2, 3}

常见的传参代码示例:

#include <stdio.h>

// 函数定义
void printArray(int arr[2][3]) {
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
int main() {
    int arr[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    printArray(arr);  // 传递二维数组
    return 0;
}

这里我们的形参写成了,int arr[2][3] 这只是我们为了方便起见,但其本质是传了一个数组指针

指针形式的函数参数

另一种表示方法是使用指针形式的函数参数:

#include <stdio.h>

// 函数定义
void printArray(int (*arr)[3], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

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

    printArray(arr, 2);  // 传递二维数组
    return 0;
}

在这个示例中,printArray函数的参数是一个指向具有3个元素的一维数组的指针,并且还接受一个表示行数的参数。

4.函数指针变量

函数指针变量是C语言中一个强大的特性,它允许你将函数的地址存储在变量中,并通过该变量调用函数。这对于实现回调、动态函数调用等功能非常有用。

我们先看一段代码,看下函数是否有地址

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

int main()
{
	printf("add  = %p\n", add);
	printf("&add = %p\n", &add);

	return 0;
}

输出结果

add = 00007FF6015213DE
&add = 00007FF6015213DE

很明显,函数也是有地址,且函数名就代表了它自身的地址。

一. 函数指针的声明

函数指针的声明形式如下:

return_type (*pointer_name)(parameter_types);

其中:

  • return_type 是函数的返回类型。
  • pointer_name 是指针变量的名称。
  • parameter_types 是函数的参数类型列表。

示例

声明一个指向返回类型为 int 且参数为 intint 的函数的指针:

int (*func_ptr)(int, int);

二. 初始化和使用函数指针

代码示例:

int add(int a, int b) 
{
    return a + b;
}

int main()
{
    int (*func_ptr)(int, int) = add;
    int result = func_ptr(2, 3); // 调用 add 函数,result 的值为 5
    printf("%d", result);

    return 0;
}

注:*func_ptr(2,3)和func_ptr(2,3)效果是相同的。

下面我们来看两段代码,尝试自己去分析以下

1.(*(void (*)())0)();
2. void (*signal(int , void(*)(int)))(int);

代码一分解解析

  1. (void (*)())0 的解析

    • (void (*)()) 表示将整数0强制类型转换为一个函数指针。
    • void (*)() 表示一个函数指针,指向一个不返回任何值(void)的函数,并且不接受任何参数。
  2. *(void (*)())0 的解析

    • *(void (*)())0 表示将地址0强制类型转换为一个函数指针,并且对该指针进行解引用。
    • 在C语言中,对地址0进行解引用通常是一种访问操作系统中的特定内存区域或硬件的方法,这通常是未定义行为,因为地址0通常是一个无效的地址,不应该被访问或解引用。
  3. (*(void (*)())0)() 的解析

    • 最外层的 () 表示调用函数指针指向的函数。
    • 因此,(*(void (*)())0)() 意味着调用地址0处的函数。

代码二分解解析

  1. void (*)(int) 的解析

    • void (*)(int) 表示一个函数指针,指向一个不返回任何值(void)的函数,并且接受一个 int 类型的参数。
  2. signal 函数的声明

    • signal 是一个函数,它接受两个参数:
      • 第一个参数是 int 类型。
      • 第二个参数是一个指向不返回任何值的函数,并且接受一个 int 参数的函数指针。
    • signal 函数返回一个函数指针,该指针指向一个不返回任何值的函数,并且接受一个 int 参数。
  3. void (*signal(int , void(*)(int)))(int)解析

代表了函数signal的返回值是**void (*)(int)**,参数是int和void(*)(int)

三.typedef关键字的使用

1. 创建新的数据类型名称

使用 typedef 可以为现有的数据类型创建一个新的名称。这种方式有助于简化复杂的类型声明,使代码更加清晰易懂。

示例:

typedef unsigned long long int ullong;  
// 创建 unsigned long long int 的别名 ullong

在这个示例中,ullong 现在代表了 unsigned long long int 这个数据类型。

2. 简化复杂的指针声明

当涉及到复杂的指针类型时,typedef 可以提高代码的可读性,使得指针的声明更加直观和易于理解。

typedef int (*operation)(int, int);

// 创建一个函数指针类型的别名 operation

示例:

int add(int a, int b) 
{
    return a + b;
}

typedef int (*operation)(int, int); 

int main()
{
    operation func_ptr = add;
    int result = func_ptr(2, 3); // 调用 add 函数,result 的值为 5
    printf("%d", result);

    return 0;
}

在这个示例中,operation 现在表示一个函数指针类型,它可以指向一个接受两个 int 参数并返回 int 类型值的函数。

5.函数指针数组

一.函数指针数组的声明

type (*FuncPtr[size])(type);

type(*)(type)代表该数组存储的函数指针类型

size表示该数组的带下

FuncPtr表示该数组的名字

二.函数指针的使用

代码示例:

#include <stdio.h>

void func1(void) {
    printf("Function 1\n");
}

void func2(void) {
    printf("Function 2\n");
}

void func3(void) {
    printf("Function 3\n");
}

// 重定义一个函数指针类型
typedef void (*FuncPtr)(void);



int main() {
    int i=0;

    // 定义函数指针数组并初始化
    FuncPtr funcArray[] = {func1, func2, func3};

    // 循环调用函数指针数组中的函数
    for (i = 0; i < 3; ++i) {
        funcArray[i]();  // 调用索引为i的函数指针指向的函数
    }

    return 0;
}

输出结果:

Function 1 Function 2 Function 3

6.转移表

一.基本概念

转移表的基本思想是,将函数指针存储在数组中,数组的索引或者数组中存储的值可以作为输入条件,通过索引或值直接调用对应的函数。

二.转移表实际应用

下面是通过转移表实现加减乘除的代码示例:

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

int sub(int x, int y)
{
	return x - y;
}

int div(int x,int y)
{
	return x / y;
}

int mul(int x, int y)
{
	return x * y;
}

void menu()
{
	printf("******************************\n");
	printf("**********  0.exit  **********\n");
	printf("***** 1.add ******2.sub*******\n");
	printf("******3.div ******4.mul*******\n");
	printf("******************************\n");
}
enum cal
{
	EXIT,
	ADD,
	SUB,
	DIV,
	MUL
};
int main()
{
	menu();
	int(*arr[5])(int, int) = { 0,add,sub,div,mul };//转移表

	int input = 0;
	do {
		input = 0;
		int x = 0;
		int y = 0;

		printf("请输入你需要的操作:");
		scanf("%d", &input);

		if (input != 0&&input>=0&&input<=5)
		{
			printf("请输入需要操作的两个数:");
			scanf("%d %d", &x, &y);
		}

		switch (input)
		{
		case EXIT:
			printf("退出成功\n");
			break;
		case ADD:
			printf("%d\n", arr[ADD](x, y));
			break;
		case SUB:
			printf("%d\n", arr[SUB](x, y));
			break;
		case DIV:
			printf("%d\n", arr[DIV](x, y));
			break;
		case MUL:
			printf("%d\n", arr[MUL](x, y));
			break;
		default:
			printf("输入错误请重新输入\n");
			break;
		}
	} while (input);


	return 0;
}
相关推荐
代码小鑫15 分钟前
A032-基于Spring Boot的健康医院门诊在线挂号系统
java·开发语言·spring boot·后端·spring·毕业设计
训山22 分钟前
4000字浅谈Java网络编程
java·开发语言·网络
API快乐传递者24 分钟前
除了网页标题,还能用爬虫抓取哪些信息?
开发语言·爬虫·python
hutaotaotao1 小时前
c语言用户不同命令调用不同函数实现
c语言·开发语言
huangjiazhi_1 小时前
QTcpSocket 服务端和客户端
开发语言·qt
lb36363636361 小时前
介绍一下位操作符(c基础)
c语言·知识点
ac-er88882 小时前
ThinkPHP中的MVC分层是什么
开发语言·php·mvc
shinelord明2 小时前
【再谈设计模式】建造者模式~对象构建的指挥家
开发语言·数据结构·设计模式
黑不拉几的小白兔2 小时前
PTA部分题目C++重练
开发语言·c++·算法
写bug的小屁孩2 小时前
websocket身份验证
开发语言·网络·c++·qt·websocket·网络协议·qt6.3