C语言 指针(特别篇)

本篇目录

  • C语言 指针(特别篇)
    • 内存地址
    • 简要介绍C语言指针
    • C语言的指针可以指向什么?
    • 取地址符 ``&``(Address-of Operator)
    • C语言中的 ``*`` 号运算符
    • 示例集:
      • 指向变量的指针
      • 指向数组的指针
      • 指向字符串的指针
      • 二级指针
        • 指针数组的数组名是一个二级指针
        • 二维数组 || 矩阵 || 二级指针
      • 指向结构体的指针
      • 动态申请内存
      • 指向函数的指针
          • 指向函数的指针压入栈中实现递归调用
      • 多级指针
        • 三级指针

C语言 指针(特别篇)

C语言是一种十分重要的编程语言,广泛应用于计算机领域,尤其是操作系统、编译器、网络通信等方面。其中,指针是C语言中非常重要的概念和基础,本文主要介绍C语言中各种指针的用法。


内存地址

学好C语言指针的关键在于要深刻理解计算机中的内存地址

计算机中的内存地址是指用来唯一标识存储单元的值。这些存储单元按照连续的方式构成了计算机的内存空间,每个存储单元可以存储一个字节(8位)的数据。

内存地址通常以十六进制表示,它们从0开始递增,直到最大地址。在32位系统中,最大地址为0xFFFFFFFF(4GB),而在64位系统中,最大地址可达到0xFFFFFFFFFFFFFFFF(18EB,1EB等于10^9GB)。

内存地址空间可分为以下几个部分:

  1. 代码段(Code Segment):
    代码段存储程序的机器指令,也称为可执行代码。它是只读的,用来存放程序的指令集和常量数据。

  2. 数据段(Data Segment):
    数据段存储程序的全局变量、静态变量和静态常量。它包含了已经初始化或默认初始化的数据,并且在程序运行期间不会发生变化。

  3. BSS段(Block Started by Symbol):
    BSS段存储未初始化的全局变量和静态变量。在程序加载时,BSS段的变量会自动被初始化为0或空指针。

  4. 堆(Heap):
    堆是动态分配内存的区域,用于存储程序运行时动态申请的数据。在C语言中,通过函数如malloc()和free()来管理堆内存的分配与释放。

  5. 栈(Stack):
    栈用于存储程序运行时的局部变量、函数参数和调用信息。栈是一种先进后出的数据结构,通过指针栈顶位置来实现栈帧的压入和弹出。

  6. 运行时堆栈:
    运行时堆栈是保存函数调用过程中使用的局部变量、中间结果和返回地址的区域。每个函数调用都会在运行时创建一个新的堆栈帧,并在函数返回时销毁。

这些内存地址空间的划分使得程序能够有效地管理内存资源,并提供了不同类型数据的存储区域。对于程序员来说,理解计算机的内存地址模型是编写高效、可靠代码的基础之一。


简要介绍C语言指针

C语言的指针是一种变量,它可以存储内存地址作为值。指针的 本质是通过存储内存地址来提供对数据的间接访问和操作能力

在计算机中,内存被划分成一个个存储单元,每个存储单元都有唯一的地址。在C语言中,指针允许我们将这些地址作为值存储起来,并通过指针来访问和修改所指向的内存单元中存储的数据。

指针的本质是在内存中存储的一个整数值,该值代表某个特定内存单元的地址。通过使用指针,我们可以直接操作内存中的数据,无需通过变量名来访问。这为程序员提供了更灵活、高效地处理数据的能力。

指针的特点有以下几点:

  1. 指针保存变量的内存地址,而不是变量的实际值。
  2. 通过指针可以直接读取或修改指向的内存单元中的数据。
  3. 指针在使用前需要进行初始化,即将指针指向特定地址。
  4. 可以通过指针进行数据的传递和共享,使得函数可以修改传入的变量的值。

总结起来,C语言的指针提供了一种强大的工具能够在程序中灵活地操作内存中的数据。通过指针,我们可以实现动态内存管理、数组和字符串处理、数据结构的构建等功能。理解指针的本质能够帮助程序员更好地利用C语言进行开发和优化。C语言指针对于一些初学者来说可能很难,但是一旦掌握了C语言指针,将会大大提高编程的效率。


C语言的指针可以指向什么?

  1. 变量:指针可以指向不同类型的变量,包括整型、浮点型、字符型等。通过指针,可以访问和修改指向变量所存储的值。
  2. 数组元素:数组名本质上是指向数组首元素的指针,可以使用指针来遍历数组,访问和修改数组元素的值。
  3. 字符串:字符串实际上是由一系列字符组成的字符数组,在C语言中以空字符(‘\0’)结尾。可以使用指针来操作字符串,包括遍历、拷贝和连接等操作。
  4. 结构体:结构体是用户自定义的复合数据类型,可以包含多个不同类型的成员。指针可以指向结构体变量,允许通过指针访问和修改结构体中的成员。
  5. 动态分配的内存:C语言提供了动态内存管理的功能,可以使用指针来指向通过malloc()、calloc()等函数动态分配的内存块,并在不需要时释放该内存。
  6. 函数:在C语言中,函数也被视为一种特殊的数据类型。指针可以指向函数,称为函数指针。通过函数指针,可以调用相应的函数及传递函数作为参数。

取地址符 &(Address-of Operator)

在C语言中,取地址符(Address-of Operator)用于获取变量的地址。取地址符使用符号"&"表示,放置在变量名之前。

以下是取地址符的使用示例:

int num = 10;
int *ptr = # // 取得变量num的地址,并将其赋值给指针ptr

printf("变量num的地址:%p\n", &num);
printf("指针ptr存储的地址:%p\n", ptr);

C语言中的 * 号运算符

在C语言中,星号(*)是一元运算符,具有多种作用,取决于它所用的上下文。以下是星号运算符在C语言中的几种常见作用:

  1. 声明指针类型:在变量声明时,星号可以用作指针类型的标识符。例如,int *ptr; 声明了一个名为ptr 的指向整型变量的指针。
  2. 解引用操作符(Dereference Operator):使用星号对指针进行解引用操作,可以访问指针所指向的内存地址存储的值。例如,int x = *ptr; 将会将指针 ptr 指向的内存地址的值赋给变量 x
  3. 定义函数指针:使用星号可以定义函数指针,用于存储函数的地址。例如,int (*funcPtr)(int, int); 定义了一个指向接受两个int类型参数并返回 int 类型值的函数的指针。
  4. 乘法运算符:星号还可以用于乘法运算,表示两个数相乘的结果,此时 * 号就变成了二元运算符。例如,int result = a * b; 将变量 ab 相乘的结果赋给变量 result

示例集:

指向变量的指针

int num = 10;
int *p;       // 定义一个整型指针
p = #     // 将指针p指向变量num的内存地址, & 为取地址符, 可以将变量的内存地址取出
printf("%d\n", *p);    // 输出变量num的值

C语言 指针(特别篇)_第1张图片

指向数组的指针

C语言中,数组名本身就是指向数组首个元素的指针,可以通过指针访问整个数组的元素。

int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;     // 数组名arr即是指向数组首个元素的指针
for (int i = 0; i < 5; i++) {
    printf("%d ", *(ptr + i));   // 输出数组元素
}

C语言 指针(特别篇)_第2张图片

数组名本身就是一个指针:

int arr[5] = {1, 2, 3, 4, 5};
printf("数组的首地址为 %d\n", arr);
for (int i = 0; i < 5; i++) {
    printf("内存地址为 %d 中存储的数据为 %d\n", arr, *(arr + i));   // 输出数组元素
}

C语言 指针(特别篇)_第3张图片

指向字符串的指针

#include 

int main() {
    char *name = "John"; // 指向字符常量"John"的指针
    printf("Name: %s\n", name); // 使用%s格式化输出字符串
    
    return 0;
}

C语言 指针(特别篇)_第4张图片

#include 

int main() {
    char *fruits[] = {"Apple", "Banana", "Orange"}; // 字符串指针数组
    int i;

    for (i = 0; i < 3; i++) {
        printf("Fruit: %s\n", fruits[i]); // 使用循环遍历并输出每个字符串
    }

    return 0;
}

C语言 指针(特别篇)_第5张图片

字符串的本质就是字符数组,也可以用指向数组的指针来操作字符串。

二级指针

二级指针 就是 指向 指针变量 的 指针。指针变量本质也是变量,只不过这个变量存储的是其他变量的内存地址罢了。既然是变量就有内存地址,所以还可以再定义一个指针变量,用再定义的指针变量来存储前面那个指针变量的内存地址,于是后定义的指针变量就指向了前面的指针变量。这个后定义的指针变量就是二级指针。

int num = 10;
int *p1 = &num;    // 指向变量num的指针
int **p2 = &p1;    // 指向指针p1的指针(二级指针)
printf("%d\n", **p2);    // 输出变量num的值

C语言 指针(特别篇)_第6张图片

指针数组的数组名是一个二级指针

#include 
#include 
int main()
{
	int i;
	char *color[5]={"red","blue","yellow","green","black"};    //字符串数组, 或者说指针数组
	char **pc;    //二级指针
	char str[20];
	pc=color;
	printf("Input a color:");
	scanf("%s",str);
	for(i=0;i<5;i++)
		if(strcmp(str,*(pc+i))==0)
		 	break;
	if(i<5)
		printf("position:%d\n",i+1);
	else
		printf("Not Found\n");
	return 0;
}

二维数组 || 矩阵 || 二级指针

#include 
#include 

// 函数:创建并初始化矩阵
int** createMatrix(int rows, int cols) {
    int** matrix = (int**)malloc(rows * sizeof(int*)); // 分配行指针数组的内存空间

    for (int i = 0; i < rows; i++) {
        matrix[i] = (int*)malloc(cols * sizeof(int)); // 分配每一行的内存空间

        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i + j; // 初始化矩阵元素的值
        }
    }

    return matrix;
}

// 函数:释放矩阵的内存空间
void freeMatrix(int** matrix, int rows) {
    for (int i = 0; i < rows; i++) {
        free(matrix[i]); // 释放每一行的内存空间
    }

    free(matrix); // 释放行指针数组的内存空间
}

// 函数:打印矩阵
void printMatrix(int** matrix, int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", matrix[i][j]); // 输出矩阵元素的值
        }
        printf("\n");
    }
}

int main() {
    int rows = 3, cols = 3;
    int** matrix = createMatrix(rows, cols);

    printf("Matrix:\n");
    printMatrix(matrix, rows, cols);

    freeMatrix(matrix, rows);

    return 0;
}

C语言 指针(特别篇)_第7张图片

指向结构体的指针

typedef struct {
    char name[20];
    int age;
} Person;

Person person;
Person *ptr = &person;    // 指向结构体Person的指针
strcpy(ptr->name, "Tom"); // 修改结构体成员name的值
ptr->age = 25;            // 修改结构体成员age的值
printf("%s %d\n", ptr->name, ptr->age);   // 输出结构体成员的值

对于指向结构体的指针可以参考我的往期文章,内容很详细 : C语言结构体数组+结构体类型指针+指向结构体数组的指针+typedef类型

动态申请内存

动态申请内存的两个函数 malloc()calloc() 函数声明包含在 stdlib.h 头文件中。

#include 
#include 

int main() {
    int size;
    int *arr;

    printf("请输入数组大小:");
    scanf("%d", &size);

    // 动态分配内存
    arr = (int *)malloc(size * sizeof(int));

    if (arr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    // 初始化数组元素
    for (int i = 0; i < size; i++) {
        arr[i] = i + 1;
    }

    // 打印数组元素
    printf("数组元素:");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }

    // 释放内存
    free(arr);

    return 0;
}

上述代码中,首先通过scanf函数获取用户输入的数组大小。然后使用malloc函数动态分配大小为size乘以sizeof(int)的内存空间,并将返回的指针赋值给整型指针变量arr。

接着,我们通过遍历数组对其元素进行初始化。最后,使用循环打印数组元素。

在程序末尾,使用free函数释放动态分配的内存空间,防止内存泄漏。

请注意,在使用完动态分配的内存后,一定要记得及时释放,以确保不会造成内存泄漏。

C语言 指针(特别篇)_第8张图片

以下是一个使用 calloc 函数的例子:

#include 
#include 

int main() {
    int size;
    int *arr;

    printf("请输入数组大小:");
    scanf("%d", &size);

    // 使用calloc动态分配内存,并初始化为零
    arr = (int *)calloc(size, sizeof(int));

    if (arr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    // 打印数组元素
    printf("数组元素:");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }

    // 释放内存
    free(arr);

    return 0;
}

在上述代码中,我们先通过scanf函数获取用户输入的数组大小。然后使用calloc函数动态分配大小为size乘以sizeof(int)的内存空间,并将返回的指针赋值给整型指针变量arr。

由于使用了calloc函数,所分配的内存会被自动初始化为零。因此,在打印数组元素时,我们可以看到初始时它们都是零。

同样地,在程序末尾,使用free函数释放动态分配的内存空间,防止内存泄漏。

C语言 指针(特别篇)_第9张图片

指向函数的指针

C语言中,函数其实也有一个入口地址。我们可以用指针来指向函数。函数入口地址是指函数在内存中的起始位置的地址。每个函数都有一个唯一的函数入口地址,它表示函数在可执行程序中的位置,让程序能够定位并调用该函数。

函数入口地址通常由编译器在编译阶段确定,并在链接器将各个模块合并成可执行程序时进行填充。当程序调用一个函数时,实际上是通过函数入口地址来跳转到该函数的代码执行处。

在C语言中,函数入口地址可以使用函数指针来表示和操作。函数指针是一个特殊类型的指针,它可以存储函数的入口地址,以便后续调用该函数。通过获取函数的入口地址,我们可以将其赋值给函数指针,并通过该指针间接调用函数。

#include 

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

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

int main() {
    int (*operation)(int, int); // 声明一个指向函数的指针

    operation = add; // 将add函数的地址赋值给指针
    printf("Result of addition: %d\n", operation(5, 3)); // 通过指针调用add函数

    operation = subtract; // 将subtract函数的地址赋值给指针
    printf("Result of subtraction: %d\n", operation(5, 3)); // 通过指针调用subtract函数

    operation = multiply; // 将multiply函数的地址赋值给指针
    printf("Result of multiplication: %d\n", operation(5, 3)); // 通过指针调用multiply函数

    return 0;
}

C语言 指针(特别篇)_第10张图片

指向函数的指针压入栈中实现递归调用

当我们运行递归函数的时候,操作系统其实做的就是将每次调用的函数地址压入栈中。

在函数递归调用时,函数指针的压入栈中可以实现递归调用的原理。具体来说,当一个函数通过函数指针调用自身时,它需要将函数指针的值压入栈中,并保留其他必要的参数和局部变量。

下面是一个简单的示例代码,展示了利用函数指针实现递归调用的原理:

#include 

// 定义递归函数
int recursiveFunc(int n, int (*func)(int)) {
    if (n <= 0) {
        return 0;
    }
    
    // 调用函数指针指向的函数,并将结果与递归调用相加
    return func(n) + recursiveFunc(n - 1, func);
}

// 定义一个打印数字的函数
int printNumber(int num) {
    printf("%d ", num);
    return num;
}

int main() {
    int n = 5;
    
    // 将打印数字的函数指针作为参数传递,并压入栈实现递归调用
    int result = recursiveFunc(n, printNumber);
    
    printf("\n结果:%d\n", result);
    
    return 0;
}

在上面的例子中,我们定义了一个递归函数recursiveFunc,它接受两个参数:n表示递归的终止条件,func表示指向函数的指针。递归函数首先判断终止条件,如果满足则返回0;否则,调用函数指针指向的函数,并将结果与递归调用的结果相加。

我们还定义了一个打印数字的函数printNumber,它接收一个整数并在控制台上打印该数字。在main函数中,我们将打印数字的函数指针作为参数传递给递归函数,并将起始值设为5。

当程序运行时,递归函数会依次调用打印数字的函数,并在每次调用时打印当前的数字。最后,递归函数的结果被打印出来。

通过将指向函数的指针作为参数传递并压入栈,可以实现函数的递归调用。这种方法可以动态地指定需要执行的函数,并在函数执行过程中保持递归的状态。

C语言 指针(特别篇)_第11张图片

多级指针

理论上指针的级数可以无限增长,但是通常没用应用的必要。记住一点,只有同级指针且指针指向的数据类型相同时,可以相互赋值

三级指针

#include 
#include 

void printMatrix(int **matrix, int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
}

void createMatrix(int ***matrix, int rows, int cols) {
    *matrix = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        (*matrix)[i] = (int *)malloc(cols * sizeof(int));
        for (int j = 0; j < cols; j++) {
            (*matrix)[i][j] = i * cols + j;
        }
    }
}

void freeMatrix(int ***matrix, int rows) {
    for (int i = 0; i < rows; i++) {
        free((*matrix)[i]);
    }
    free(*matrix);
    *matrix = NULL;
}

int main() {
    int **matrix = NULL;
    int rows = 3;
    int cols = 3;

    createMatrix(&matrix, rows, cols);

    printf("Matrix:\n");
    printMatrix(matrix, rows, cols);

    freeMatrix(&matrix, rows);

    return 0;
}

在这个例子中,我们使用三级指针 int ***matrix 来操控矩阵。首先,在 createMatrix 函数中,我们通过传递指向指针的指针来分配内存并创建矩阵。然后,在 printMatrix 函数中,我们使用二级指针 int **matrix 来遍历和打印矩阵的元素。最后,在 freeMatrix 函数中,我们使用三级指针来释放矩阵占用的内存。

请注意,在操作三级指针时需要小心管理内存,并确保正确地分配和释放内存,以避免内存泄漏和错误。

三级指针的应用
三维数组在编程中有许多应用场景,特别是在涉及到多维数据的存储和处理时非常有用。以下是一些常见的三维数组的应用示例:

  1. 三维图像处理:在计算机图形学和图像处理领域,三维数组经常用于表示和处理彩色图像或体积数据。图像可以被看作是由像素组成的二维阵列,而每个像素又包含红、绿、蓝(RGB)或其他颜色通道的值,这样就可以使用三维数组来表示图像数据。

  2. 三维空间建模:在三维建模、游戏开发和虚拟现实中,三维数组可以用于表示三维空间中的物体、场景或地形。例如,一个三维场景可以被分成一个网格,每个网格单元包含物体的属性、纹理信息或碰撞检测数据等。

  3. 多维物理模拟:在物理模拟和科学计算中,三维数组可以用于存储和更新三维空间中的物理量,如速度场、压力场或温度场。通过使用三维数组,可以对不同位置上的物理量进行存储和操作,并模拟复杂的物理过程。

  4. 数据立方体:三维数组还可以用于表示和分析包含多个维度的数据集,例如销售数据、气象数据或市场调查数据。这些数据通常以数据立方体(data cube)的形式进行分析和查询,其中三维数组的每个维度对应于数据集中的一个属性。

这些只是三维数组的一些常见应用示例,实际上,它们在各种领域和问题中都具有广泛的应用。使用三维数组时,需要理解索引和访问元素的方式,并根据具体的问题进行适当的操作和算法设计。

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