C 语言 “神秘魔杖”—— 指针初相识,解锁编程魔法大门(一)

news2024/12/4 15:29:06

文章目录

  • 一、概念
    • 1、取地址操作符(&)
    • 2、解引用操作符(*)
    • 3、指针变量
      • 1、 声明和初始化
      • 2、 用途
  • 二、内存和地址
  • 三、指针变量类型的意义
    • 1、 指针变量类型的基本含义
    • 2、 举例说明不同类型指针变量的意义
  • 四、const修饰指针
    • 1、const修饰指针的三种情况
  • 五、指针运算
    • 1、指针的算术运算
    • 2、指针的关系运算
  • 六、野指针
    • 1、野指针的定义
    • 2、野指针的示例
    • 3、造成野指针的原因
    • 4、如何避免野指针
  • 七、assert断言
    • 1、assert断言的基本概念
  • 八、指针的使用和传址调用
    • 1、指针的使用
    • 2、传址调用

一、概念

  • 指针是C语言中的一个重要概念,它是一个变量,其值为另一个变量的地址。简单来说,指针“指向”了内存中的某个位置,这个位置存储着其他变量的值。就好像你有一个房间号(指针),通过这个房间号可以找到房间里存放的东西(变量的值);也就是可以通过告诉我们房间号(指针),快速的找到房间里的内容(变量的值)。

1、取地址操作符(&)

  • 含义
    • 取地址操作符&用于获取变量在内存中的地址。在C语言中,每个变量都被存储在内存中的某个位置,这个位置有一个唯一的地址。通过&操作符,我们可以得到这个地址的值。
  • 示例
    • 以下是一个简单的示例代码,展示如何使用取地址操作符。
#include <stdio.h>
int main() {
    int num = 10;
    // 使用取地址操作符获取num的地址
    int *ptr = &num;
    printf("The address of num is: %p\n", (void*)&num);
    return 0;
}
 - 在这个例子中,定义了一个整型变量`num`并初始化为`10`。
 - 然后通过`&num`获取`num`的地址,并将这个地址赋值给一个指针变量`ptr`。
 - `%p`是用于打印指针(地址)的格式化说明符,由于`printf`函数的参数要求,将`&num`强制转换为`(void*)`类型。

2、解引用操作符(*)

  • 含义
    • 解引用操作符*用于访问指针所指向的内存位置中的值。当我们有一个指针变量,它存储了一个变量的地址,通过解引用操作符可以获取该地址所对应的实际值。
  • 示例
    • 以下是一个示例,演示了解引用操作符的使用。
#include <stdio.h>
int main() {
    int num = 10;
    int *ptr = &num;
    // 使用解引用操作符获取ptr指向的内存中的值
    printf("The value pointed to by ptr is: %d\n", *ptr);
    return 0;
}
 - 在这里,首先定义了一个整型变量`num`和一个指向`int`类型的指针`ptr`,并将`num`的地址赋值给`ptr`。
 - 然后通过`*ptr`来解引用指针,获取`ptr`所指向的内存位置(也就是`num`所在的内存位置)中的值,
 - 并将其打印出来。这个值就是`10`,因为`ptr`指向`num`,而`num`的值为`10`。

总的来说,取地址操作符&和解引用操作符*是C语言中操作指针的重要工具,它们经常一起配合使用,使得程序员能够灵活地处理内存中的数据。

3、指针变量

  • 指针变量是一种特殊的变量,它存储的是另一个变量的内存地址。在C语言中,变量在内存中有自己的存储位置,这个位置可以通过地址来表示。指针变量就像是一个指向其他变量的“指示器”,它本身占用一定的内存空间,用于存放目标变量的地址。

1、 声明和初始化

  • 声明格式
    • 数据类型 *指针变量名;
    • 例如,int *p;声明了一个名为p的指针变量,它可以用来存储int类型变量的地址。这里的*是一个说明符,表示p是一个指针变量,它指向的数据类型是int
  • 初始化
    • 指针变量在使用前最好进行初始化。可以通过取地址操作符&来获取变量的地址并赋值给指针变量。例如:
int num = 10;
int *p = &num;
 - 在这个例子中,先定义了一个整型变量`num`并赋值为`10`,然后定义了一个指针变量`p`,
 - 并通过`&num`将`num`的地址赋值给`p`,这样`p`就指向了`num`。

2、 用途

  • 访问变量的值
    • 通过解引用指针变量(使用*操作符)可以访问它所指向变量的值。例如:
#include <stdio.h>
int main() {
    int num = 10;
    int *p = &num;
    // 解引用指针p来获取num的值
    printf("The value of num through pointer is %d\n", *p);
    return 0;
}
 - 这里,`*p`获取了`p`所指向的变量(即`num`)的值,然后将其打印出来。
  • 函数参数传递
    • 指针变量在函数参数传递中有重要作用。当我们想要在函数内部修改外部变量的值时,可以将变量的地址传递给函数,函数通过指针来访问和修改该变量。例如:
#include <stdio.h>
// 函数用于交换两个整数的值
void swap(int *a, int *b) {
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}
int main() {
    int x = 5, y = 10;
    printf("Before swap: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("After swap: x = %d, y = %d\n", x, y);
    return 0;
}
 - 在这个例子中,`swap`函数接受两个指针变量`a`和`b`,通过解引用这些指针来交换它们所指向的变量的值。
 - 在`main`函数中,通过`&x`和`&y`将`x`和`y`的地址传递给`swap`函数,从而实现了`x`和`y`值的交换。
  • 了解指针中的操作符,我们来举一个例子:假设有一个整型变量int num = 10;,可以定义一个指针来指向这个变量。int *p;这里p就是一个指针,它可以用来存储num的地址。通过p=&num;操作,就使得p指向了num在这里插入图片描述

二、内存和地址

  • 在计算机中,内存就像是一个个小格子(字节),每个格子都有一个唯一的编号,这个编号就是地址。变量存储在内存中,指针存储的就是这些变量的内存地址。也就是说,指针就是这个内存单元的地址。
    在这里插入图片描述

  • 例如,对于上面的num变量,它在内存中有一个地址。如果num的地址是0x100(这是假设的十六进制地址),那么p的值在执行p = &num;后就会变为0x100。可以通过printf("%p\n", p);来打印p所指向的地址。
    在这里插入图片描述

三、指针变量类型的意义

1、 指针变量类型的基本含义

  • 在C语言中,指针变量的类型非常重要。指针变量的类型决定了它所指向的数据类型。例如,int *类型的指针是用来指向int类型的数据,char *类型的指针是用来指向char类型的数据。
  • 这种类型规定了指针在进行解引用操作时,编译器会按照该类型所对应的字节数来访问内存。不同数据类型在内存中占用的字节数不同,比如在大多数32位系统中,int类型通常占用4个字节,char类型占用1个字节。

2、 举例说明不同类型指针变量的意义

  • 指向整数的指针(int *
    • 示例代码:
#include <stdio.h>
int main() {
    int num = 10;
    int *ptr = &num;
    // 解引用指针,获取整数的值
    printf("The value of num through pointer is %d\n", *ptr);
    // 指针加法运算
    ptr++;
    // 此时ptr指向的地址向后偏移了一个int类型的大小(通常为4字节)
    // 这里只是展示指针类型对偏移量的影响,实际这样使用可能会导致未定义行为
    // 因为ptr已经指向了num之后的内存位置,而这个位置可能是未分配给程序使用的
    return 0;
}
 - 在这个例子中,`int *ptr`声明了一个指向`int`类型的指针`ptr`。当`ptr`被初始化为`&num`后,通过`*ptr`解引用可以获取`num`的值。
 - 当执行`ptr++`时,指针`ptr`会在内存中向后移动`sizeof(int)`个字节(在常见系统中是4字节),这是因为`ptr`是`int *`类型,
 - 编译器知道`int`类型数据的大小,所以会按照这个大小来移动指针。
  • 指向字符的指针(char *
    • 示例代码:
#include <stdio.h>
int main() {
    char ch = 'A';
    char *cptr = &ch;
    printf("The character through pointer is %c\n", *cptr);
    cptr++;
    // 此时cptr指向的地址向后偏移了一个char类型的大小(1字节)
    return 0;
}
 - 对于`char *cptr`,它指向`char`类型的数据。当`cptr`被初始化为`&ch`后,通过`*cptr`可以获取`ch`的值。执行`cptr++`时,
 - 指针会在内存中向后移动`sizeof(char)`个字节(1字节),这是因为`char`类型数据在内存中占用1字节,
 - 指针的移动是根据其指向的数据类型来确定的。
  • 指向结构体的指针(struct xxx *
    • 假设我们有一个简单的结构体:
#include <stdio.h>
struct Student {
    char name[20];
    int age;
};
int main() {
    struct Student stu = {"John", 20};
    struct Student *sptr = &stu;
    // 解引用结构体指针,访问结构体成员
    printf("The student's name is %s and age is %d\n", sptr->name, sptr->age);
    return 0;
}
 - 在这里,`struct Student *sptr`是一个指向`struct Student`类型结构体的指针。
 - 当它被初始化为`&stu`后,通过`->`操作符(这是一种用于结构体指针访问成员的便捷操作符,
 - 等价于`(*sptr).name`和`(*sptr).age`)可以访问结构体`stu`中的成员。如果要移动这个指针,
 - 编译器会按照`struct Student`类型结构体的大小(这个大小是`name`成员数组的大小加上`age`成员占用的大小)来移动指针。

四、const修饰指针

1、const修饰指针的三种情况

  • 指向常量的指针(const修饰所指对象)
    • 含义
      • 这种情况下,指针所指向的数据被视为常量,不能通过该指针来修改所指向的数据,但指针本身可以重新指向其他内存单元。
    • 语法形式
      • const 数据类型 *指针变量名;,例如const int *p;
    • 示例
#include <stdio.h>
int main() {
    int num1 = 10;
    int num2 = 20;
    const int *p = &num1;
    // 下面这行代码是错误的,因为不能通过p修改它所指向的数据
    // *p = 30;
    p = &num2;  // 这是可以的,指针p可以重新指向num2
    printf("The value pointed to by p is %d\n", *p);
    return 0;
}
 - 在这个例子中,`p`是一个指向`const int`类型的指针,也就是`p`所指向的`int`数据被当作常量。
 - 当试图通过`*p = 30`修改`p`所指向的数据时,编译器会报错。但可以将`p`重新指向`num2`,因为只是改变了指针的指向,
 - 而没有违反`const`的限制。
  • 常量指针(const修饰指针本身)
    • 含义
      • 指针本身是常量,它不能再重新指向其他内存单元,但可以通过该指针修改它所指向的数据。
    • 语法形式
      • 数据类型 *const指针变量名;,例如int *const p;
    • 示例
#include <stdio.h>
int main() {
    int num1 = 10;
    int *const p = &num1;
    *p = 30;  // 这是可以的,能够修改p所指向的数据
    // 下面这行代码是错误的,因为p是常量指针,不能重新指向其他内存单元
    // p = &num2;
    printf("The value of num1 after modification is %d\n", num1);
    return 0;
}
 - 这里,`p`是一个常量指针,它被初始化为指向`num1`后,就不能再指向其他变量了。
 - 不过,可以通过`*p = 30`修改`p`所指向的`num1`的值,因为`const`修饰的是指针本身,而不是它所指向的数据。
  • 指向常量的常量指针(const同时修饰指针和所指对象)
    • 含义
      • 指针本身不能重新指向其他内存单元,并且不能通过该指针修改所指向的数据。
    • 语法形式
      • const 数据类型 *const指针变量名;,例如const int *const p;
    • 示例
#include <stdio.h>
int main() {
    int num1 = 10;
    const int *const p = &num1;
    // 下面这行代码是错误的,不能通过p修改所指向的数据
    // *p = 30;
    // 下面这行代码也是错误的,p不能重新指向其他内存单元
    // p = &num2;
    return 0;
}
 - 在这个例子中,`p`是一个指向常量的常量指针。既不能通过`*p`修改`p`所指向的数据,也不能让`p`重新指向其他内存单元,
 - `p`的指向和它所指向的数据都被`const`修饰固定了。
  • 指针可以进行算术运算,如加法、减法等。当指针加上或减去一个整数时,它实际移动的字节数是该整数乘以指针所指向数据类型的大小。
  • 例如,对于一个int *p,如果p的值是0x100,执行p++;后,p的值会增加sizeof(int)字节。在32位系统中,int占4字节,所以p的值会变为0x100 + 4 = 0x104
  • 指针还可以进行减法运算,用来计算两个指针之间的距离。例如,有两个指针p1p2指向同一个数组中的元素,int *p1, *p2;p1 - p2的结果是两个指针之间元素的个数。

五、指针运算

1、指针的算术运算

  • 指针与整数相加/相减
    • 含义
      • 当指针与一个整数进行加法或减法运算时,指针移动的字节数是该指针所指向的数据类型大小乘以这个整数。例如,对于一个int *类型的指针(假设int类型占4字节),如果与整数n相加,那么指针在内存中实际向前移动4 * n字节。
    • 示例
#include <stdio.h>
int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *p = &arr[0];
    // 指针p向后移动2个int类型的长度
    p = p + 2;
    printf("The value pointed to by p after addition is %d\n", *p);
    // 指针p向前移动1个int类型的长度
    p = p - 1;
    printf("The value pointed to by p after subtraction is %d\n", *p);
    return 0;
}
 - 在这个例子中,首先定义了一个整型数组`arr`,并让指针`p`指向数组的第一个元素`arr[0]`。
 - 当执行`p = p + 2`时,由于`p`是`int *`类型,`int`类型通常占4字节,
 - 所以`p`在内存中向后移动了`2 * 4 = 8`字节,此时`p`指向`arr[2]`,输出为`3`。
 - 接着执行`p = p - 1`,`p`向前移动了4字节,此时`p`指向`arr[1]`,输出为`2`。
  • 两个指针相减
    • 含义
      • 两个相同类型的指针相减,结果是两个指针之间相隔的元素个数,而不是字节数。这个运算的结果类型是ptrdiff_t(在<stddef.h>头文件中定义的有符号整数类型)。这个运算通常用于计算数组中两个元素之间的距离。
    • 示例
#include <stdio.h>
#include <stddef.h>
int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *p1 = &arr[1];
    int *p2 = &arr[3];
    ptrdiff_t diff = p2 - p1;
    printf("The difference between p2 and p1 is %td\n", diff);
    return 0;
}
 - 在这里,定义了数组`arr`,`p1`指向`arr[1]`,`p2`指向`arr[3]`。
 - 当计算`p2 - p1`时,结果是`2`,因为`p2`和`p1`之间相隔2个`int`类型的元素。

2、指针的关系运算

  • 含义
    • 指针可以进行关系运算(如><>=<===!=),用于比较两个指针在内存中的位置关系。通常用于判断指针是否指向同一个数组中的元素,或者判断一个指针是否超出了某个数组的范围。
  • 示例
#include <stdio.h>
int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *p1 = &arr[0];
    int *p2 = &arr[3];
    if (p2 > p1) {
        printf("p2 points to a later element in the array than p1\n");
    }
    return 0;
}
 - 在这个例子中,定义了数组`arr`,`p1`指向`arr[0]`,`p2`指向`arr[3]`。
 - 通过比较`p2 > p1`,因为`p2`在内存中指向的位置在`p1`之后,所以条件成立,会输出相应的信息。

需要注意的是,指针运算应该在合理的范围内进行,特别是对于非数组元素的指针进行算术运算可能会导致未定义行为。

六、野指针

1、野指针的定义

  • 野指针是指指针变量指向的内存位置是不确定的(随机的、不可预知的)。野指针不是NULL指针,NULL指针明确地指向地址为0的内存空间,而野指针指向的位置是未定义的,可能是已经被释放的内存区域、未分配的内存区域或者其他非法的内存地址。使用野指针可能会导致程序崩溃、数据损坏或者产生不可预测的行为。

2、野指针的示例

  • 下面是一个产生野指针的简单示例:
#include <stdio.h>
int main() {
    int *p;
    // 此时p是一个未初始化的指针,它的值是随机的,是野指针
    *p = 10;
    return 0;
}
  • 在这个例子中,定义了一个指针变量p,但是没有对它进行初始化。然后就试图通过*p = 10来修改p所指向的内存位置的值。由于p没有被初始化,它指向的是一个随机的内存地址,这个操作可能会导致程序崩溃或者出现其他错误。

3、造成野指针的原因

  • 指针变量未初始化
    • 这是最常见的原因之一。当定义一个指针变量时,如果没有给它赋初值,它的值是随机的,就会成为野指针。例如上面的例子中,int *p;定义后没有初始化p就直接使用。
  • 指针所指向的内存被释放后,指针没有置为NULL
    • 当使用free()函数(在动态内存分配中)释放了指针所指向的内存后,指针本身的值(即内存地址)并没有改变,它仍然指向原来的内存位置。而这个内存位置已经被释放,再通过这个指针访问内存就会出现问题。例如:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *p = (int *)malloc(sizeof(int));
    if (p!= NULL) {
        *p = 10;
        // 释放内存
        free(p);
        // 此时p成为野指针,因为它仍然指向刚刚被释放的内存位置
        *p = 20; 
    }
    return 0;
}
  • 在这个例子中,通过malloc函数分配了一块内存给p,然后给这块内存赋值为10。接着使用free函数释放了这块内存,但是p仍然指向这块被释放的内存。当试图再次通过*p = 20修改这个内存位置的值时,就会出现错误,因为这块内存已经被释放,此时p是野指针。

4、如何避免野指针

  • 初始化指针变量
    • 尽量在定义指针变量时就对它进行初始化。如果暂时不知道指针应该指向哪里,可以将它初始化为NULL。例如int *p = NULL;,这样在使用p之前就可以通过判断p == NULL来确定是否可以安全地进行解引用操作。
  • 及时将释放内存后的指针置为NULL
    • 在使用free()函数释放指针所指向的内存后,应该立即将指针赋值为NULL。例如:
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *p = (int *)malloc(sizeof(int));
    if (p!= NULL) {
        *p = 10;
        free(p);
        p = NULL;
        // 此时p为NULL,后续代码如果不小心使用p,就可以通过判断p == NULL避免错误操作
    }
    return 0;
}
  • 注意指针的作用域和生命周期
    • 确保指针在其指向的对象的生命周期内有效。例如,在一个函数内部定义的局部指针,在函数返回后,如果这个指针指向的是函数内部的局部变量,那么这个指针就不应该再被使用,因为函数返回后,局部变量的内存已经被释放。# 七、assert断言
  • assert是一个宏,用于在程序调试阶段检查一个条件是否为真。当条件为假时,程序会终止并输出错误信息。在涉及指针时,可以使用assert来检查指针是否为NULL等情况。
  • 例如:
    int *p = NULL;
    assert(p!= NULL);  // 程序会在这里终止,因为p为NULL
    
  • 通常在程序开发过程中,使用assert可以帮助发现一些潜在的指针错误,提高程序的健壮性。不过在发布版本中,可能会因为性能等原因关闭assert检查。

七、assert断言

1、assert断言的基本概念

  • 定义
    • assert是一个在C语言标准库<assert.h>中定义的宏。它用于在程序开发过程中进行调试检查,帮助程序员验证程序中的假设是否成立。其主要功能是检查一个表达式的值是否为真,如果表达式的值为假(即为0),则程序会以一种“标准错误”的方式终止,并输出相关的错误信息,包括断言失败的表达式、文件名和行号等内容。
  • 目的
    • 用于捕捉程序中的逻辑错误,特别是在程序开发阶段,可以帮助定位那些不应该出现的错误情况。例如,在使用指针时,可以通过assert来检查指针是否为NULL,以避免因为空指针解引用而导致的程序崩溃。
  1. 使用assert断言的示例
    • 检查指针是否为NULL
      • 示例代码如下:
#include <stdio.h>
#include <assert.h>
int main() {
    int *p = NULL;
    // 使用assert检查指针p是否为NULL
    assert(p!= NULL);
    // 如果p为NULL,程序会在这里终止,并输出错误信息
    // 因为p是NULL,所以下面这行代码不会执行
    *p = 10;
    return 0;
}
 - 在这个例子中,首先定义了一个指针`p`并将其初始化为`NULL`。
 - 然后使用`assert(p!= NULL)`来检查`p`是否不为`NULL`。由于`p`实际上是`NULL`,所以这个断言会失败。
 - 当断言失败时,程序会终止,并输出类似于以下的错误信息(具体格式可能因编译器和环境而异):
 - `Assertion failed: p!= NULL, file main.c, line 7`
 - 这表明在`main.c`文件的第7行,`p!= NULL`这个断言不成立。
 - 这样就可以很容易地定位到错误发生的位置,并且知道是由于指针为`NULL`导致的问题。
  • 检查数组索引是否越界(结合指针运算)
    • 假设我们有一个函数,用于遍历一个整数数组并打印元素,代码如下:
#include <stdio.h>
#include <assert.h>
void print_array(int *arr, int size) {
    int i;
    // 对于每个合法的索引,打印数组元素
    for (i = 0; i < size; i++) {
        assert((arr + i) >= arr);
        // 检查指针是否还在数组范围内
        printf("%d ", *(arr + i));
    }
}
int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    print_array(arr, size);
    return 0;
}
 - 在`print_array`函数中,`for`循环用于遍历数组。
 - 在每次循环中,使用`assert((arr + i) >= arr)`来检查`arr + i`这个指针是否还在数组范围内。
 - `arr + i`表示数组`arr`中第`i`个元素的地址,它应该总是大于或等于数组的起始地址`arr`。
 - 如果在循环过程中由于某种原因(例如数组索引计算错误)导致`arr + i`小于`arr`,
 - 断言就会失败,程序会终止并输出错误信息,帮助我们发现数组索引越界的问题。

八、指针的使用和传址调用

1、指针的使用

  • 访问变量
    • 基本原理
      • 通过指针可以间接访问它所指向的变量。首先要获取变量的地址,然后将这个地址存储在指针变量中,最后通过解引用指针来访问变量的值。
    • 示例
#include <stdio.h>
int main() {
    int num = 10;
    int *p = &num;
    // 解引用指针p获取num的值
    printf("The value of num through pointer is %d\n", *p);
    return 0;
}
 - 在这个例子中,`int *p = &num;`声明了一个指针`p`并使其指向变量`num`。`*p`用于解引用指针,从而获取`p`所指向变量(即`num`)的值。
  • 动态内存分配
    • 基本原理
      • C语言中的malloccallocrealloc函数用于动态分配内存,返回的是指向所分配内存块的指针。malloc函数分配指定字节数的内存,calloc函数在分配内存的同时将内存块初始化为0,realloc函数用于重新分配已经分配过的内存块的大小。
    • 示例(使用malloc
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *arr;
    int n = 5;
    // 分配能容纳5个整数的内存空间
    arr = (int *)malloc(n * sizeof(int));
    if (arr == NULL) {
        // 检查内存分配是否成功
        printf("Memory allocation failed!\n");
        return -1;
    }
    // 使用分配的内存
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    // 释放内存
    free(arr);
    return 0;
}
 - 这里,`arr = (int *)malloc(n * sizeof(int));`分配了足够容纳`n`个`int`类型数据的内存空间。
 - 如果`arr`不为`NULL`,则表示内存分配成功,可以像使用普通数组一样使用`arr`。
 - 最后,使用`free(arr)`释放分配的内存。

2、传址调用

  • 基本原理
    • 在C语言中,函数参数传递默认是值传递,即函数会复制一份实参的值作为形参。传址调用则是将变量的地址作为参数传递给函数。这样,函数内部可以通过指针来访问和修改外部变量的值。
  • 示例(交换两个数)
#include <stdio.h>
// 交换两个整数的函数,使用传址调用
void swap(int *a, int *b) {
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}
int main() {
    int x = 5, y = 10;
    printf("Before swap: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("After swap: x = %d, y = %d\n", x, y);
    return 0;
}
 - 在这个例子中,`swap`函数接受两个指针参数`a`和`b`。在`main`函数中,通过`swap(&x, &y)`将`x`和`y`的地址传递给`swap`函数。在`swap`函数内部,通过解引用指针`*a`和`*b`来访问和交换`x`和`y`的值。这样,就实现了在函数内部修改外部变量的值。
  • 通过传址调用返回多个值
    • 示例(计算一个数的平方和立方)
#include <stdio.h>
// 计算一个数的平方和立方,通过指针返回结果
void square_and_cube(int num, int *square, int *cube) {
    *square = num * num;
    *cube = num * num * num;
}
int main() {
    int num = 3;
    int square, cube;
    square_and_cube(num, &square, &cube);
    printf("The square of %d is %d and the cube is %d\n", num, square, cube);
    return 0;
}
 - 这里,`square_and_cube`函数接受一个整数`num`和两个指针`square`和`cube`。
 - 函数内部通过解引用指针来将计算结果(`num`的平方和立方)
 - 存储在`main`函数中定义的`square`和`cube`变量中,从而实现了通过一个函数返回多个值的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2253112.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

封装loding加载动画的请求

图片 /*** Loading 状态管理类*/ export class Loading {constructor(timer300) {this.value falsethis.timer timer}/*** 执行异步操作并自动管理 loading 状态* param {Promise|Function|any} target - Promise、函数或其他值* returns {Promise} - 返回请求结果*/async r…

人形机器人训练、机器臂远程操控、VR游戏交互、影视动画制作,一副手套全部解决!

广州虚拟动力基于自研技术推出了多节点mHand Pro动捕数据手套&#xff0c;其最大的特点就是功能集成与高精度捕捉&#xff0c;可以用于人形机器人训练、机器臂远程操控、VR游戏交互、影视动画制作等多种场景。 一、人形机器人训练 mHand Pro动捕数据手套双手共装配16个9轴惯性…

Nginx Web服务器管理、均衡负载、访问控制与跨域问题

Nginx Web 服务器的均衡负载、访问控制与跨域问题 Nginx 的配置 1. 安装Nginx 首先安装Nginx apt install nginx -ycaccpurgatory-v:~$ sudo apt install nginx [sudo] password for cacc: Reading package lists... Done Building dependency tree... Done Reading state i…

Bert+CRF的NER实战

CRF&#xff08;条件随机场-Conditional Random Field&#xff09; 原始本文&#xff1a;我在北京吃炸酱面 标注示例&#xff08;采用BIO标注方式&#xff09;&#xff1a; 我O在O北B-PLA京I-PLA吃O炸B-FOOD酱I-FOOD面I-FOOD CRF&#xff1a; 目的&#xff1a;提出一些不可能…

C++语法·识

人生建议&#xff1a;请手机反省一下&#xff0c;为什么总拉着我熬夜。 目录 STL简介 string类容器一 auto&#xff08;自动声明类型&#xff09; 简介&#xff1a; 特点 范围for&#xff08;语法糖&#xff09; 简介 特点 string string类的常见接口 1.构造 2.容…

蓝桥杯准备训练(lesson1,c++方向)

前言 报名参加了蓝桥杯&#xff08;c&#xff09;方向的宝子们&#xff0c;今天我将与大家一起努力参赛&#xff0c;后序会与大家分享我的学习情况&#xff0c;我将从最基础的内容开始学习&#xff0c;带大家打好基础&#xff0c;在每节课后都会有练习题&#xff0c;刚开始的练…

【开源】A059-基于SpringBoot的社区养老服务系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看项目链接获取⬇️&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600个选题ex…

winform跨线程更新界面

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在开发C#程序的时候&#xff0c;有时候需要在非Ui主线程更新界面&#xff0c;为了…

无界(wujie)微前端项目搭建,nginx线上部署,pnpm一键安装依赖、启动应用,git代码仓库存放方式

这里写自定义目录标题 1. 创建项目项目目录布局选择主应用子应用 2. pnpm包管理&#xff0c;一键安装、启动、打包pnpm一键安装依赖npm-run-all 一键启动、打包 3. nginx线上部署主应用中子应用中nginx文件目录及配置 git代码存放方式 1. 创建项目 主应用&#xff1a; vue3vit…

10.容器-list列表

定义一个list使用[] 定义一个空列表 [] 或者 list() 列表中每个元素之间用逗号隔开 a_list [aa, bb, cc] print(a_list) # <class list> print(type(a_list)) list列表可以存储不同类型的元素 a_list [aa, bb, cc] print(a_list) # <class list> print(type…

BiGRU:双向门控循环单元在序列处理中的深度探索

一、引言 在当今的人工智能领域&#xff0c;序列数据的处理是一个极为重要的任务&#xff0c;涵盖了自然语言处理、语音识别、时间序列分析等多个关键领域。循环神经网络&#xff08;RNN&#xff09;及其衍生结构在处理序列数据方面发挥了重要作用。然而&#xff0c;传统的 RN…

PDF与PDF/A的区别及如何使用Python实现它们之间的相互转换

目录 概述 PDF/A 是什么&#xff1f;与 PDF 有何不同&#xff1f; 用于实现 PDF 与 PDF/A 相互转换的 Python 库 Python 实现 PDF 转 PDF/A 将 PDF 转换为 PDF/A-1a 将 PDF 转换为 PDF/A-1b 将 PDF 转换为 PDF/A-2a 将 PDF 转换为 PDF/A-2b 将 PDF 转换为 PDF/A-3a 将…

计费结算系统的架构设计思路

背景 近期负责关于集团的计费结算相关的系统&#xff0c;相对于2C系统的大流量&#xff0c;高并发的场景&#xff0c;计费和结算的信息对稳定性要求更高。对时效性要求并没有过于严苛的要求。那么接下来就和大家分享一下计费结算系统的架构设计。 模块划分 我们暂且将平台细分…

人工智障(5)

今天kimi把我气疯了&#xff0c;你们看原对话&#xff1a; 月之暗面最近在搞什么&#xff0c;不仅算力慢&#xff0c;而且回答离谱的要死&#xff0c;难道换老板了&#xff1f;

Python爬虫——城市数据分析与市场潜能计算(Pandas库)

使用Python进行城市市场潜能分析 简介 本教程将指导您如何使用Python和Pandas库来处理城市数据&#xff0c;包括GDP、面积和城市间距离。我们将计算每个城市的市场潜能&#xff0c;这有助于了解各城市的经济影响力。 步骤 1: 准备环境 确保您的环境中安装了Python和以下库&…

Python毕业设计选题:基于Flask的医疗预约与诊断系统

开发语言&#xff1a;Python框架&#xff1a;flaskPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 系统首页 疾病信息 就诊信息 个人中心 管理员登录界面 管理员功能界面 用户界面 医生…

Android 图形系统之二:ViewRootImpl

ViewRootImpl简介 ViewRootImpl 是 Android UI 系统的核心类之一&#xff0c;负责将 View 层级树与窗口管理器 WindowManager 联系起来。它是Android 应用视图的根节点&#xff0c;与 WindowManager 结合&#xff0c;实现视图的绘制、事件分发、窗口更新等功能。虽然 ViewRoot…

python通过ODBC连接神通数据库

1、安装神通数据库 2、安装python 3、安装pyodbc pip3 install pyodbc-5.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl 注&#xff1a;pyodbc要和python版本相对应 4、安装unixodbc 5、配置神通数据库ODBC数据源 6、示例代码如下 #!/usr/bin/python…

基于单片机的智能药箱设计

本设计主要由红外检测传感器、显示、独立按键、舵机、语音以及短信等模块组成。红外传感器模块主要对药仓中的药物数据进行采集&#xff0c;采集完毕由主控制器进行数据加工&#xff0c;之后可传送至显示模块上进行显示&#xff0c;在显示模块也可对显示时间、吃药倒计时、吃药…

【掩体计划——DFS+缩点】

题目 代码 #include <bits/stdc.h> using namespace std; const int N 1e5 10; vector<vector<int>> g; bool st[N]; int ans 1e9; bool dfs(int f, int u, int dis) {bool is 1;for (auto j : g[u]){if (j f)continue;is & dfs(u, j, dis (g[u].…