当前位置: 首页 > news >正文

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语言中用于获取变量内存地址的一元操作符。它的使用非常直观:只需在变量名前加上&符号。

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 ,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量中。

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

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是一个指向整数指针的指针(二级指针)

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

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 解引用操作符的作用

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

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 解引用的应用场景

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

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

例如:

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

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

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

2.3 指针变量的大小

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

  • 32位系统:所有类型的指针通常占用4字节
  • 64位系统:所有类型的指针通常占用8字节
#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:

#include <stdio.h>int main() {int x = 1001;int* ptr = &x;*ptr = 20; // 通过指针修改x的值printf("x = %d\n", x); // 输出:x = 20printf("*ptr = %d\n", *ptr); // 输出:*ptr = 20
}

在这里插入图片描述

代码2:

#include <stdio.h>int main() {int x = 1001;char* ptr = (char *) & x;*ptr = 20; // 通过指针修改x的值printf("x = %d\n", x); // 输出:x = 20printf("*ptr = %d\n", *ptr); // 输出:*ptr = 20
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

解引用的重要性:

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

注意事项:

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

3.2 指针+ - 整数

观察一下代码

#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语言中一个强大而复杂的特性。

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* 类型的指针不能直接进行指针的±整数和解引用的运算。

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);

在这里插入图片描述

这样便是错误的:

#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 指针+ - 整数

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

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
数组1020304050
下标01234

在这里插入图片描述

注意事项:

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

4.2 指针 - 指针

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

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

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 50if (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

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

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

5.2 常量指针(constant pointer)

语法:int* const ptr

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

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

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

语法:const int* const ptr

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

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

总结

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

6. 野指针(Dangling Pointer)

6.1 什么是野指针?

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

6.2 野指针的成因

6.2.1 未初始化的指针

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

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

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

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

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

6.2.3 指针超出变量作用域

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

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

6.2.4 返回栈上内存的地址

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

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

6.3 野指针的危害

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

6.4 如何规避野指针

6.4.1 初始化指针

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

int* ptr = NULL;  // 良好习惯

6.4.2 释放内存后将指针置空

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

int* ptr = (int*)malloc(sizeof(int));
free(ptr);
ptr = NULL;  // 防止成为野指针

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

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

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

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

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

#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的基本语法

#include <assert.h>
int main()
{assert(expression);return 0;
}

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

示例:

assert(p != NULL);

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

7.3 assert的工作原理

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

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

7.4 assert的优点

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

7.5 禁用assert

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

#define NDEBUG
#include <assert.h>

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


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

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

#define NDEBUG
#include <assert.h>

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

8 指针的使用和传址调用

8.1 strlen的模拟实现

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

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 传值调用

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

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 = 10return 0;
}

8.2.2 传址调用

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

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 = 5return 0;
}

传址调用通常用于:

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

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

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

#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. 传址调用(有效的交换)

#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 程序至关重要。

—完—


http://www.mrgr.cn/news/64450.html

相关文章:

  • 深度学习(神经网络)中模型的评估与性能度量
  • 【十九周】文献阅读:图像识别的深度残差学习
  • 使用Django REST framework构建RESTful API
  • Hive数据库操作语法
  • Vue3(重点笔记)
  • 统信UOS设备驱动开发-常见问题
  • 05 Django 框架模型介绍(一)
  • 虚拟机安装Ubuntu系统
  • 网络请求优化:理论与实践
  • 【Python项目管理】“无法创建虚拟环境”报错原因及解决方法
  • JZ2440开发板——LCD
  • 什么是软件测试?软件测试的流程?软件测试未来3-5年的职业规划?
  • 【AD】1-2 AD24软件的中英文版本切换
  • Python数据分析案例62——基于MAGU-LSTM的时间序列预测(记忆增强门控单元)
  • 不同网线类型
  • 数据库->联合查询
  • Ubuntu使用Qt虚拟键盘,支持中英文切换
  • 网鼎杯-re2-好久不见5
  • C语言 ——— 学习和使用 strstr 函数,并模拟实现
  • [Redis] Redis事务
  • 高频电子线路---一文读懂调幅
  • Ubuntu - 进入紧急模式,无法进入桌面
  • [RootersCTF2019]ImgXweb
  • Golang--DOS命令、变量、基本数据类型、标识符
  • 图文深入介绍Oracle DB link(二)
  • 【资调实习报告】华中农业大学资源调查与评价实习报告