C语言第11节:指针(1)

1. 内存和地址

1.1 内存

内存是计算机系统中用于存储数据和指令的硬件设备。它可以被视为一个巨大的、有序的字节数组。

  • 基本单位:内存的基本单位是字节(byte)。每个字节由8个位(bit)组成,可以存储0到255之间的一个数值。
  • 内存模型:从程序员的角度来看,内存可以被想象成一个巨大的一维数组,每个元素都是一个字节。
  • 内存容量:现代计算机的内存容量通常以GB(千兆字节)或TB(太字节)为单位。例如,8GB的内存意味着有8,589,934,592个字节可供使用。
  • 内存类型:主要包括随机访问内存(RAM)和只读内存(ROM)。RAM是易失性存储器,断电后数据会丢失;ROM是非易失性存储器,断电后数据仍然保留。

内存的工作原理对于理解C语言中的指针概念至关重要。每当我们在程序中声明一个变量时,实际上是在内存中分配了一块空间来存储这个变量的值。

1.2 究竟该如何理解编址

编址是为内存中的每个字节分配唯一标识符(地址)的过程。这个过程使得CPU能够准确地访问和操作内存中的数据。

  • 地址空间:地址空间是指可能的地址范围。在32位系统中,地址空间是2^32 (约42亿)个不同的地址,而在64位系统中,这个数字增加到2^{64}。
  • 地址表示:内存地址通常以十六进制表示,因为十六进制更容易转换为二进制,而计算机内部使用二进制。例如,十六进制地址0x1000等同于十进制的4096。
  • 字节寻址:大多数现代计算机系统使用字节寻址,这意味着每个字节都有一个唯一的地址。例如,一个32位整数占用4个字节,因此会有4个连续的地址与之关联。
  • 大端序和小端序:这两种方式决定了多字节数据类型(如int或float)在内存中的存储顺序。大端序将最高有效字节存储在最低地址,而小端序则相反。

理解编址对于有效使用指针和进行底层内存操作至关重要。它让我们能够直接访问和操作内存中的数据,这是C语言强大功能的基础。

2. 指针变量和地址

2.1 取地址操作符(&)

取地址操作符&是C语言中用于获取变量内存地址的一元操作符。它的使用非常直观:只需在变量名前加上&符号。

c 复制代码
int x = 10;
int* ptr = &x; // ptr现在存储x的地址
printf("x的值:%d\n", x);
printf("x的地址:%p\n", (void*)&x);
printf("ptr存储的地址:%p\n", (void*)ptr);

在上面的代码中:

  • x是一个整型变量,值为10。
  • &x返回x的内存地址。
  • ptr是一个指针变量,存储了x的地址。
  • %p是用于打印指针(地址)的格式说明符。

需要注意的是:

  • 不是所有的表达式都可以取地址。例如,常量和某些表达式就不能取地址。
  • 取地址操作符只能用于内存中的对象,不能用于寄存器变量。

2.2 指针变量和解引用操作符(*)

2.2.1 指针变量

那我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x006FFD70 ,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量中。

指针变量是一种特殊的变量,用于存储内存地址。它的声明包括两部分:指针所指向的数据类型和星号(*)。

c 复制代码
int* p;    // 指向整数的指针
char* c;   // 指向字符的指针
float* f;  // 指向浮点数的指针
void* v;   // 无类型指针,可以指向任何类型的数据

指针变量的特点:

  • 指针变量本身也占用内存空间,通常是4字节(32位系统)或8字节(64位系统)。
  • 指针变量可以被赋值、比较、进行算术运算等。
  • 未初始化的指针变量包含垃圾值,使用它们可能导致程序崩溃或不可预知的行为。

2.2.2 如何拆解指针类型

指针类型由两部分组成:基本类型和星号(*)。理解这种结构有助于正确声明和使用指针。

  • int* ptr: ptr是一个指向整数的指针
  • char* str: str是一个指向字符的指针,通常用于表示字符串
  • float* fptr: fptr是一个指向浮点数的指针
  • int** pptr: pptr是一个指向整数指针的指针(二级指针)

解引用操作符(*)用于访问指针所指向的值:

c 复制代码
int x = 10;
int* ptr = &x;
printf("x的值:%d\n", x);
printf("通过指针访问x的值:%d\n", *ptr);
*ptr = 20; // 通过指针修改x的值
printf("修改后x的值:%d\n", x);

在这个例子中,*ptr不仅可以用来读取x的值,还可以用来修改x的值。这展示了指针强大的间接访问能力。

2.2.3 解引用操作符

2.2.3.1 解引用操作符的作用

解引用操作符 * 的主要作用是通过指针访问数据 。当我们有一个指针时,指针存储的是一个内存地址,而不是实际的数据。使用 * 可以告诉程序从这个地址中获取实际的数据。

c 复制代码
int x = 10;
int *p = &x;  // p是指向x的指针
int y = *p;   // 解引用p,获得x的值

在上面的代码中:

  • int *p = &x;:这行代码将 x 的地址赋给了指针 p,使 p 指向 x
  • int y = *p;:使用解引用操作符 *,我们获取了指针 p 所指向的值,也就是 x 的值,将它赋给变量 y
2.2.3.2 解引用的应用场景

解引用操作符在以下场景中特别有用:

  • 访问指针指向的数据:当我们有一个指针并希望通过它访问它所指向的数据时,解引用操作符就派上用场。
  • 指针修改数据:通过解引用指针,我们可以直接修改它所指向的内存地址中的数据。

例如:

c 复制代码
*p = 20;  // 通过指针p修改x的值为20
  • 在动态内存分配中使用:动态内存分配返回的是指针,通过解引用我们可以访问和操作动态分配的内存。
2.2.3.3 解引用的注意事项

使用解引用操作符时,有几点需要注意:

  • 指针必须指向有效的内存地址:如果指针未被初始化或指向了非法地址(如 NULL),解引用操作会导致运行时错误,甚至程序崩溃。
  • 避免野指针:当指针指向的内存已经被释放,仍然去解引用会产生野指针问题,这可能导致未定义的行为。(后面会讲解)
  • 解引用不同类型的指针:解引用操作符会按照指针类型去解读数据。因此,指针类型必须与其指向的数据类型一致,否则可能导致错误的数据访问。

2.3 指针变量的大小

指针变量的大小与系统的架构密切相关,而不是与它所指向的数据类型有关。

  • 32位系统:所有类型的指针通常占用4字节
  • 64位系统:所有类型的指针通常占用8字节
c 复制代码
#include <stdio.h>

int main() {
    int* p1;
    char* p2;
    double* p3;
    void* p4;

    printf("int*    大小: %zu 字节\n", sizeof(p1));
    printf("char*   大小: %zu 字节\n", sizeof(p2));
    printf("double* 大小: %zu 字节\n", sizeof(p3));
    printf("void*   大小: %zu 字节\n", sizeof(p4));

    return 0;
}

3. 指针变量类型的意义

指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,为什么还要有各种各样的指针类型呢?但其实指针类型是有特殊意义的。

3.1 指针的解引用

解引用是通过指针访问它所指向的内存位置的过程。解引用操作符*用于这个目的。

代码1:

c 复制代码
#include <stdio.h>

int main() {
    int x = 1001;
    int* ptr = &x;
    *ptr = 20; // 通过指针修改x的值

    printf("x = %d\n", x); // 输出:x = 20
    printf("*ptr = %d\n", *ptr); // 输出:*ptr = 20
}

代码2:

c 复制代码
#include <stdio.h>

int main() {
    int x = 1001;
    char* ptr = (char *) & x;
    *ptr = 20; // 通过指针修改x的值

    printf("x = %d\n", x); // 输出:x = 20
    printf("*ptr = %d\n", *ptr); // 输出:*ptr = 20
}

结论:指针的类型决定了对指针解引用的时候有多大的权限(一次能操作几个字节)。

解引用的重要性:

  • 允许间接访问和修改数据
  • 是实现动态内存分配的基础
  • 使得复杂的数据结构(如链表、树等)的实现成为可能

注意事项:

  • 解引用空指针或未初始化的指针会导致未定义行为,可能造成程序崩溃
  • 在使用指针之前,始终确保它指向有效的内存位置

3.2 指针+ - 整数

观察一下代码

c 复制代码
#include <stdio.h>
int main()
{
	int a = 123;
	int* p1 = &a;
	char* pc = (char *)&a;
	printf("&n =\t\t %p\n", &a);
	printf("p1 =\t\t %p\n", p1);
	printf("p1 + 1 =\t %p\n", p1 + 1);
	printf("pc =\t\t %p\n", pc);
	printf("pc + 1 =\t %p\n", pc + 1);
}

我们可以看出,char* 类型的指针变量+1跳过1个字节,int* 类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。指针的算术运算是C语言中一个强大而复杂的特性。

c 复制代码
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr; // ptr指向数组的第一个元素

printf("%d\n", *ptr);    // 输出:10
ptr++;                   // 移动到下一个整数位置
printf("%d\n", *ptr);    // 输出:20
ptr += 2;                // 向前跳过两个整数
printf("%d\n", *ptr);    // 输出:40

指针算术的关键点:

  • ptr++使指针移动到下一个元素,而不是下一个字节
  • 移动的字节数等于指针类型的大小(例如,对于int*,通常是4字节)
  • 这种行为使得数组遍历变得简单高效

结论 :当对指针进行加减运算时,实际的内存偏移量取决于指针的类型

3.3 void* 指针

void*是一种特殊的指针类型,可以指向任何类型的数据。它通常被称为"通用指针"。但是也有局限性,void* 类型的指针不能直接进行指针的±整数和解引用的运算。

c 复制代码
int i = 10;
float f = 3.14;
char c = 'A';

void* ptr;

ptr = &i;
printf("整数值:%d\n", *(int*)ptr);

ptr = &f;
printf("浮点数值:%f\n", *(float*)ptr);

ptr = &c;
printf("字符值:%c\n", *(char*)ptr);

这样便是错误的:

c 复制代码
#include <stdio.h>
int main()
{
	int a = 10;
	void *pa = &a;
	void *pc = &a;
	*pa = 10;
	*pc = 0;
	return 0;
}

void*指针的特点:

  • 可以存储任何类型的地址,提供了极大的灵活性
  • 不能直接解引用,必须先转换为具体的类型
  • 常用于通用内存分配函数(如malloc)的返回值
  • 在进行指针算术时需要特别小心,因为void*没有具体的大小信息

4. 指针运算

4.1 指针+ - 整数

指针的加法运算是基于指针类型的大小进行的。当对指针进行加法操作时,地址的增加量等于指针类型的大小乘以添加的整数值。

c 复制代码
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;

printf("%d\n", *ptr);     // 输出:10
printf("%d\n", *(ptr+2)); // 输出:30
printf("%d\n", ptr[2]);   // 输出:30 (等同于 *(ptr+2))

ptr += 3;                 // 移动3个整数位置
printf("%d\n", *ptr);     // 输出:40
数组 10 20 30 40 50
下标 0 1 2 3 4

注意事项:

  • 指针加法不改变原始数组
  • 确保不要越界访问数组
  • 指针减法的工作原理类似,但是向反方向移动

4.2 指针 - 指针

两个相同类型的指针相减会得到它们之间的元素数量,而不是字节数。这个操作通常用于计算数组中元素的距离。

c 复制代码
int arr[5] = {10, 20, 30, 40, 50};
int* p1 = &arr[1];
int* p2 = &arr[4];

ptrdiff_t diff = p2 - p1; // diff = 3
printf("p2和p1之间的元素数:%td\n", diff);

// 使用指针遍历数组
for(int* p = arr; p < arr + 5; p++) {
    printf("%d ", *p);
}

关键点:

  • 指针相减的结果类型是ptrdiff_t,这是一个有符号整数类型
  • 只有同类型的指针才能相减
  • 这种操作对于处理数组和动态分配的内存非常有用

4.3 指针的关系运算

指针可以使用关系运算符(<, <=, >, >=, ==, !=)进行比较。这在处理数组和其他连续内存结构时特别有用。

  • **相等性比较(== 和 !=):**用于判断两个指针是否指向同一内存位置。
  • **大小比较(<, >, <=, >=):**用于比较指针所指向的内存地址的相对位置。
  • **指向同一数组的指针:**可以进行所有关系运算。
  • **不同数组的指针:**只能进行相等性比较,其他比较可能导致未定义行为。

指针的关系运算在C语言中有着重要的应用,特别是在数组操作和内存管理中。以下是一个简单的示例,展示了指针关系运算的使用:

c 复制代码
int arr[5] = {10, 20, 30, 40, 50};
int* start = arr;
int* end = arr + 5;

while (start < end) {
    printf("%d ", *start);
    start++;
}
// 输出:10 20 30 40 50

if (start == end) {
    printf("\n遍历完成\n");
}

start = arr;
int* middle = arr + 2;
if (middle > start && middle < end) {
    printf("middle 指向数组中间元素\n");
}

在这个例子中:

  • 我们使用指针的小于运算符(<)来遍历数组。这种方法比使用索引更加高效,因为它直接操作内存地址。
  • 等于运算符(==)用于检查是否已经遍历完整个数组。
  • 大于(>)和小于运算符一起使用,可以检查一个指针是否位于其他两个指针之间。

指针关系运算的注意事项:

  • 只有指向同一数组(或同一内存块)的指针之间的比较才有意义。
  • 不同类型的指针之间的比较通常没有意义,可能导致未定义行为。
  • 空指针(NULL)可以与任何指针进行比较,通常用于检查指针是否有效。

指针关系运算在数组边界检查、链表操作和其他涉及内存管理的场景中非常有用。正确使用这些运算可以提高代码的效率和安全性。

5. const 修饰指针

5.1 指向常量的指针(pointer to constant)

语法:const int* ptrint const* ptr

特点:指针指向的值不能通过指针来修改,但指针本身可以指向其他地方。

c 复制代码
const int* ptr = &value;
*ptr = 10;  // 错误!不能通过ptr修改所指向的值
ptr = &anotherValue;  // 正确,可以改变指针指向

5.2 常量指针(constant pointer)

语法:int* const ptr

特点:指针本身的值(即所指向的地址)不能改变,但可以通过指针修改所指向的值。

c 复制代码
int value = 5;
int* const ptr = &value;
*ptr = 10;  // 正确,可以通过ptr修改所指向的值
ptr = &anotherValue;  // 错误!不能改变指针指向

5.3 指向常量的常量指针(constant pointer to constant)

语法:const int* const ptr

特点:指针本身的值不能改变,指针指向的值也不能通过指针来修改。

c 复制代码
const int* const ptr = &value;
*ptr = 10;  // 错误!不能通过ptr修改所指向的值
ptr = &anotherValue;  // 错误!不能改变指针指向

总结

  • 记忆技巧:const在*左边修饰指向的值,在*右边修饰指针本身。
  • 使用const修饰指针可以增加代码的安全性和可读性。
  • 在函数参数中使用const指针可以防止函数意外修改传入的数据。

6. 野指针(Dangling Pointer)

6.1 什么是野指针?

野指针是指向"无效"内存区域的指针。当一个指针指向的内存已经被释放或者访问超出了变量的作用域,但该指针仍然被使用时,就会产生野指针。野指针是一种非常危险的编程错误,因为它们可能导致不可预测的程序行为。

6.2 野指针的成因

6.2.1 未初始化的指针

当声明一个指针但没有给它赋予一个有效的地址时,这个指针就成为了野指针。

c 复制代码
int* ptr;  // 未初始化的指针
*ptr = 10;  // 危险!ptr指向的是未知的内存位置

6.2.2 指针所指向的内存被释放

当使用free释放了指针所指向的内存,但没有将指针置为NULL时,就会产生野指针。

c 复制代码
int* ptr = (int*)malloc(sizeof(int));
*ptr = 5;
free(ptr);  // 内存被释放
// ptr现在是野指针
*ptr = 10;  // 危险!访问已释放的内存

6.2.3 指针超出变量作用域

当函数返回一个指向局部变量的指针时,该指针会成为野指针,因为局部变量在函数结束时会被销毁。

c 复制代码
int* getLocalVar() {
    int x = 5;
    return &x;  // 危险!返回局部变量的地址
}

6.2.4 返回栈上内存的地址

类似于上一点,但特指返回栈上分配的数组或对象的地址。

c 复制代码
int* createArray() {
    int arr[10] = {0};
    return arr;  // 危险!返回栈上数组的地址
}

6.3 野指针的危害

  • 程序崩溃:访问无效内存可能导致程序立即崩溃。
  • 数据损坏:写入无效内存可能破坏其他变量的数据。
  • 安全漏洞:可能被攻击者利用来执行恶意代码。
  • 难以调试:野指针引起的问题可能在程序的其他部分显现,使得错误难以定位。
  • 不确定的行为:程序可能看似正常运行,但实际上存在潜在的危险。

6.4 如何规避野指针

6.4.1 初始化指针

始终在声明指针时进行初始化,如果暂时没有有效地址,可以将其设置为NULL。

c 复制代码
int* ptr = NULL;  // 良好习惯

6.4.2 释放内存后将指针置空

手动管理内存时,在释放后立即将指针设为NULL。

c 复制代码
int* ptr = (int*)malloc(sizeof(int));
free(ptr);
ptr = NULL;  // 防止成为野指针

6.4.3 避免返回局部变量的地址

函数返回值应使用值传递或在堆上分配内存。

c 复制代码
int* createInt() {
    int* ptr = (int*)malloc(sizeof(int));
    *ptr = 5;
    return ptr;  // 返回堆内存地址,但记得在使用后释放
}

6.4.4 使用断言检查指针有效性

在关键操作前使用断言检查指针是否为NULL。

c 复制代码
#include <assert.h>

void usePointer(int* ptr) {
    assert(ptr != NULL);
    // 使用ptr
}

6.4.5 代码审查和静态分析

定期进行代码审查,使用静态分析工具(如Clang Static Analyzer、Cppcheck等)来检测潜在的野指针问题。

6.5 总结

野指针是C语言编程中的一个常见且危险的问题。通过遵循良好的编程实践、仔细管理内存、保持警惕,我们可以大大减少野指针带来的风险。理解并正确处理指针是编写健壮、安全的C程序的关键。持续学习和实践是避免野指针陷阱的最佳方法。

7. assert 断言

7.1 什么是assert断言?

assert是C语言中的一个宏,定义在<assert.h>头文件中。它用于在程序中插入诊断点,帮助开发者发现程序中的逻辑错误。

7.2 assert的基本语法

c 复制代码
#include <assert.h>
int main()
{
    assert(expression);
    return 0;
}

如果expression为假(即为0),assert会输出错误信息并终止程序执行。

示例:

c 复制代码
assert(p != NULL);

上面代码在程序运行到这一行语句时,验证变量p 是否等于NULL 。如果确实不等于NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示。

7.3 assert的工作原理

当assert的条件为假时,它会:

  • 向stderr打印一条错误信息,包含文件名、行号和失败的表达式。
  • 调用abort()函数终止程序执行。

7.4 assert的优点

  • 帮助快速定位bug
  • 提高代码的自文档化程度
  • 在开发阶段捕获逻辑错误
  • 可以在发布版本中轻松禁用

7.5 禁用assert

如果已经确认程序没有问题,不需要再做断言,就在#include <assert.h> 语句的前面,定义一个宏NDEBUG

c 复制代码
#define NDEBUG
#include <assert.h>

这样可以通过定义NDEBUG宏来禁用所有的assert()


assert()一般我们可以在Debug 中使用,在Release 版本中选择禁用assert 就行(在VS 这样的集成开发环境中,在Release 版本中,直接就是优化掉了。)这样在debug版本写有利于程序员排查问题,在Release 版本不影响用户使用时程序的效率。

Release版本中,可以也通过定义NDEBUG宏来禁用所有的assert:

c 复制代码
#define NDEBUG
#include <assert.h>

或者在编译时使用-DNDEBUG选项。

8 指针的使用和传址调用

8.1 strlen的模拟实现

strlen是一个常用的字符串处理函数,用于计算字符串的长度(不包括结尾的空字符'\0')。让我们深入探讨如何使用指针来模拟实现这个函数:

c 复制代码
size_t my_strlen(const char* str) {
    const char* s;
    for (s = str; *s; ++s) {}
    return (s - str);
}

让我们逐步分析这个实现:

  • 函数参数:const char* str 表示一个指向常量字符的指针,确保函数不会修改原字符串。

  • 初始化:const char* s; 声明另一个指针 s,用于遍历字符串。

  • 遍历循环:

    for (s = str; *s; ++s) {}
    
    • s 初始化为与 str 相同的地址
    • *s 为真(非零)时继续循环,即直到遇到'\0'
    • 每次迭代 s 向后移动一个字符
  • 长度计算:return (s - str); 利用指针减法计算遍历的字符数

这种实现方法高效且简洁,充分利用了指针的特性。它避免了使用额外的计数变量,直接通过指针的移动来确定字符串长度。

需要注意的是,这个实现假设输入的字符串是以'\0'结尾的。如果传入的是一个无效的字符串(没有结束符),这个函数可能会导致未定义行为。在实际应用中,可能需要添加额外的安全检查。

8.2 传值调用和传址调用

在C语言中,函数参数的传递有两种主要方式:传值调用和传址调用。

8.2.1 传值调用

传值调用时,函数接收的是参数的副本。函数内部对参数的修改不会影响原始值。

c 复制代码
void swap_value(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 5, y = 10;
    swap_value(x, y);
    printf("x = %d, y = %d\n", x, y);  // 输出: x = 5, y = 10
    return 0;
}

8.2.2 传址调用

传址调用时,函数接收的是参数的地址。函数可以通过这个地址修改原始值。

c 复制代码
void swap_address(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5, y = 10;
    swap_address(&x, &y);
    printf("x = %d, y = %d\n", x, y);  // 输出: x = 10, y = 5
    return 0;
}

传址调用通常用于:

  • 需要在函数内修改传入变量的值
  • 传递大型结构体以提高效率
  • 返回多个值(通过指针参数)

8.3 编写函数:交换两个变量的值

1. 传值调用(无效的交换)

c 复制代码
#include <stdio.h>
void swap_value(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 5, y = 10;
    printf("Before swap: x = %d, y = %d\n", x, y);
    swap_value(x, y);
    printf("After swap: x = %d, y = %d\n", x, y);
    return 0;
}

让我们详细分析这个传值调用的过程:

  1. 当调用 swap_value(x, y) 时,xy 的值被复制到函数的参数 ab 中。
  2. 在函数内部,ab 是局部变量,它们只在函数内部有效。
  3. 函数执行交换操作,但这只影响 ab 的值,不会改变 main 函数中的 xy
  4. 函数结束后,ab 被销毁,它们的值丢失。
  5. main 函数中的 xy 保持不变。

输出结果:

Before swap: x = 5, y = 10
After swap: x = 5, y = 10

这种方法无法实现交换,因为函数操作的是参数的副本,而不是原始变量。

2. 传址调用(有效的交换)

c 复制代码
#include <stdio.h>
void swap_address(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5, y = 10;
    printf("Before swap: x = %d, y = %d\n", x, y);
    swap_address(&x, &y);
    printf("After swap: x = %d, y = %d\n", x, y);
    return 0;
}

现在让我们详细分析传址调用的过程:

  1. 调用 swap_address(&x, &y) 时,xy 的地址被传递给函数。
  2. 函数参数 ab 是指针,它们存储了 xy 的地址。
  3. *a 表示 "a 指向的值",即 x 的值;*b 表示 "b 指向的值",即 y 的值。
  4. 通过操作 *a*b,函数直接修改了 xy 的值。
  5. temp 用于临时存储 *a 的值,确保交换过程中不会丢失数据。
  6. 交换完成后,xy 的值在 main 函数中被成功交换。

输出结果:

Before swap: x = 5, y = 10
After swap: x = 10, y = 5

这种方法成功实现了交换,因为函数通过指针直接操作了原始变量。

传值调用与传址调用的对比

特性 传值调用 传址调用
参数传递方式 复制值 传递地址
函数内修改 不影响原变量 直接修改原变量
内存使用 创建新的副本,可能占用更多内存 只传递地址,内存效率更高
适用场景 不需要修改原变量时 需要修改原变量或传递大型数据结构时
安全性 较高,原数据不会被意外修改 需要小心处理,可能导致意外修改

总结

  1. 传值调用:
  • 函数接收参数的副本
  • 无法修改原始变量
  • 适用于简单的数据传递,不需要修改原始数据的场景
  1. 传址调用:
  • 函数接收参数的地址
  • 可以通过指针修改原始变量
  • 适用于需要在函数内修改变量值或传递大型数据结构的场景

在实际编程中,选择合适的调用方式取决于具体需求。传址调用在需要修改原始数据或处理大型数据结构时特别有用,但使用时需要格外小心,以避免意外修改数据。理解这两种调用方式的区别和适用场景,对于编写高效、正确的 C 程序至关重要。

---完---

相关推荐
XH华4 小时前
初识C语言之二维数组(下)
c语言·算法
Uu_05kkq8 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法
嵌入式科普10 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
A懿轩A10 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
1 9 J11 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
仍然探索未知中12 小时前
C语言经典100例
c语言
爱吃西瓜的小菜鸡13 小时前
【C语言】矩阵乘法
c语言·学习·算法
Stark、14 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端
deja vu水中芭蕾15 小时前
嵌入式C面试
c语言·开发语言
stm 学习ing16 小时前
HDLBits训练3
c语言·经验分享·笔记·算法·fpga·eda·verilog hdl