C++ Part1-基础概念-QA

1. C++的#define 宏常量的概念

  1. 宏常量的概念: 宏常量是一种由预处理器定义的标识符,其表示一个固定的值或表达式。在代码中使用宏常量时,预处理器会在编译之前将它们替换为其定义的值或表达式。

  2. 通俗解释: 想象你正在写一本书,每次你要用到某个词汇时,你都会使用缩写代替它,这样你就不需要在每次出现的地方都写出完整的词汇。宏常量就像书中的缩写,你可以给一个值或表达式起一个名字,这样你就可以在代码中多次使用这个名字,而不需要重复写出其实际的值或表达式。

  3. 为什么要搞出这个概念:

    • 提高代码的可读性和可维护性:使用宏常量可以使代码更易读,因为它们提供了有意义的名称来表示特定的值或表达式。
    • 减少代码中的魔法数值(Magic Number):通过将常用的数值或表达式定义为宏常量,可以减少代码中的硬编码,提高代码的可维护性和可重用性。
  4. 没有这个概念会如何: 如果没有宏常量的概念,程序员需要在代码中多次使用相同的值或表达式,并且必须确保在每次使用时都正确输入相同的数值或表达式。这样会增加代码的重复性和出错的可能性,并且降低了代码的可维护性。

#include <iostream>

#define PI 3.14159
#define MAX_VALUE 100

int main() {
    std::cout << "PI is approximately: " << PI << std::endl;
    
    int numbers[MAX_VALUE];

    return 0;
}

在这个例子中,#define 指令分别定义了两个宏常量 PIMAX_VALUE。在 main 函数中,PI 被用来表示圆周率,而 MAX_VALUE 被用来定义一个数组的最大长度。这样,当我们在代码中使用 PIMAX_VALUE 时,预处理器会将它们替换为其定义的值,分别是 3.14159100

 

2. 预处理器是什么?不是已经有了处理器了么,为什么还需要它?另外它和编译的关系是什么?

预处理器是编译器的一个组成部分,负责在实际的编译过程之前对源代码进行一些预处理操作。它是一个独立于编译器的工具,用于执行一系列指令,这些指令通常以 # 开头,例如 #define#include 等。下面解释一下为什么需要预处理器以及它与编译的关系:

预处理器的作用:

  1. 处理 # 开头的预处理指令: 预处理器主要负责处理以 # 开头的预处理指令,例如宏定义、包含文件、条件编译等。它根据这些指令对源代码进行处理,生成经过修改的代码供编译器使用。

  2. 宏替换: 预处理器执行宏替换,将代码中的宏常量或宏函数替换为其定义的值或代码片段。这有助于提高代码的可读性和可维护性。

  3. 包含文件: 预处理器处理 #include 指令,将指定的文件内容插入到当前文件中,形成单个源文件。这样可以将代码分割为多个文件,提高代码的组织性和复用性。

  4. 条件编译: 预处理器执行条件编译,根据条件判断指令选择性地包含或排除代码块,从而在不同的编译条件下生成不同的代码。

为什么需要预处理器?

  • 提供更高级的语言特性: 预处理器使得 C/C++ 等编程语言能够具备更高级的语言特性,例如宏替换、文件包含、条件编译等。这些特性增强了编程语言的灵活性和功能性。

  • 增强代码的模块化和可维护性: 使用预处理器能够将代码分割为多个模块,每个模块专注于特定的功能,提高了代码的模块化程度和可维护性。

  • 实现跨平台编程: 预处理器允许根据不同的编译条件生成不同的代码,从而实现跨平台编程,使得相同的源代码可以在不同的平台上编译和运行。

预处理器与编译器的关系:

  • 预处理器是编译过程的一部分: 预处理器在编译过程之前执行,它负责对源代码进行一些预处理操作,生成经过修改的代码。然后编译器会将预处理后的代码进行编译,生成目标代码。

  • 预处理器生成编译器所需的源代码: 预处理器根据预处理指令对源代码进行处理,生成编译器所需的源代码。这些经过预处理后的代码会被编译器编译成机器语言或中间代码,最终生成可执行文件。

  • 预处理器和编译器通常由同一个编译工具链提供: 尽管预处理器和编译器是不同的功能模块,但它们通常由同一个编译工具链提供,例如 GCC、Clang 等。因此,在编译过程中,预处理器和编译器会协同工作,完成对源代码的处理和编译。

 

3. 在一个C++程序中,一个代码从执行到最后返回结果,中间经历的所有过程有哪些?

一个代码从执行到最后返回结果,中间经历的主要过程包括:预处理、编译、链接、执行。下面我会详细解释每个过程:

1. 预处理 (Preprocessing):

  • 预处理器处理源代码:预处理器根据源代码中的预处理指令,如 #include#define 等,进行相应的处理,比如将头文件包含进来,展开宏定义等。

  • 生成预处理后的代码:预处理器生成经过处理后的代码文件,通常以 .i.ii 扩展名结尾,这个文件包含了所有预处理后的内容。

2. 编译 (Compilation):

  • 编译器将预处理后的代码转换为汇编代码:编译器将预处理后的代码转换为汇编语言代码,这是一种较低级的代码形式,与特定的处理器架构相关。

  • 汇编器将汇编代码转换为机器代码:汇编器将汇编语言代码转换为机器语言代码(二进制代码),这是计算机可以直接执行的代码形式。

  • 生成目标文件:编译器将生成的机器代码保存为目标文件,通常以 .o.obj 扩展名结尾,这些文件包含了编译后的代码和一些必要的元信息。

3. 链接 (Linking):

  • 连接器将目标文件和库文件链接为可执行文件:连接器将所有的目标文件以及需要的库文件链接在一起,生成可执行文件。它会解析符号引用,将各个模块之间的引用关系解决,并将所有模块合并成一个整体。

  • 解析全局符号引用:连接器会解析程序中的全局符号引用(如函数和变量),并将其关联到正确的定义上。

  • 生成可执行文件:连接器最终生成可执行文件,通常是一个可执行程序,该程序包含了所有链接后的代码和数据。

4. 执行 (Execution):

  • 操作系统加载可执行文件到内存中:操作系统负责将生成的可执行文件加载到内存中,准备执行。

  • 程序执行:CPU开始执行加载到内存中的可执行文件,按照指令序列执行程序中的指令。

  • 返回结果:程序执行完成后,会返回一个结果,这个结果可以是函数的返回值、状态码等,也可以是在标准输出中打印的信息。

以上就是一个代码从执行到最后返回结果的主要过程。每个过程都是编程中非常重要的环节,它们共同构成了代码的完整生命周期。

 

4. 那么在python中,一个代码从执行到最后返回结果,中间经历的所有过程有哪些,请全部详细说明,最后给出其与c++的差异

在Python中,一个代码从执行到最后返回结果,中间经历的主要过程与C++有些许不同。下面我将详细说明这些过程,然后列出与C++的差异:

1. 解释与编译:

  • 解释型语言:Python是一种解释型语言,因此在执行之前不需要显式的编译步骤。Python代码会被解释器逐行解释并执行。

2. 解释器执行过程:

  • 词法分析与语法分析:Python解释器首先会对源代码进行词法分析和语法分析,将其转换为抽象语法树(AST)。

  • 字节码生成:解释器将抽象语法树转换为字节码,这是一种与特定Python版本相关的中间表示形式。

  • 字节码执行:Python解释器执行生成的字节码,逐条执行指令。

3. 执行过程中的优化:

  • 即时编译(JIT):某些Python解释器(如CPython的一些变体)可以使用即时编译技术,将频繁执行的字节码转换为本地机器码,以提高性能。

4. 返回结果:

  • 程序执行完成后,返回结果:Python程序执行完成后,会返回一个结果,可以是函数的返回值、标准输出的打印信息等。

与C++的差异:

  1. 编译方式不同:Python是解释型语言,不需要显式的编译步骤,而C++是编译型语言,需要先将源代码编译成机器代码才能执行。

  2. 执行方式不同:Python代码是逐行解释执行的,而C++代码是按照编译后的指令直接执行的。

  3. 优化方式不同:Python解释器可以使用即时编译技术进行优化,而C++编译器可以进行更多类型的优化,如静态编译优化、链接时优化等。

  4. 执行效率不同:由于Python是解释型语言,其执行速度通常比C++慢。C++代码经过编译优化后,执行效率较高。

总的来说,Python和C++在代码执行的过程中有着明显的差异,主要体现在解释与编译、执行方式和执行效率上。

 

4.  sizeof()不同类型返回结果的理解

  1. size of short: 2:这一行输出了short类型的大小,它占据了2个字节的内存空间。在计算机中,1个字节等于8个比特(bit),所以short类型占据了16个比特的内存空间。

  2. size of int: 4:这一行输出了int类型的大小,它占据了4个字节的内存空间。因此,int类型在计算机中占据了32个比特的内存空间。

  3. size of long: 4:这一行输出了long类型的大小,它占据了4个字节的内存空间。因此,long类型在计算机中占据了32个比特的内存空间。

  4. size of long long: 8:这一行输出了long long类型的大小,它占据了8个字节的内存空间。因此,long long类型在计算机中占据了64个比特的内存空间。

 

5. 在计算机中,1个字节等于8个比特(bit)?不论什么架构,什么操作系统这个结论都成立么?

在绝大多数现代计算机系统中,1个字节等于8个比特(bit),这是普遍适用的规范。不论是哪种计算机架构(如x86、ARM等),以及使用哪种操作系统(如Windows、Linux等),通常都遵循这个规范。因此,可以认为这个结论在绝大多数情况下都成立。

然而,也有一些特殊情况,比如一些嵌入式系统或者特殊定制的硬件,可能会使用非标准的字节大小或者非典型的位宽度。但是在一般的个人计算机、服务器、移动设备等常见的计算机系统中,1个字节等于8个比特的规范是适用的。

 

6. return EXIT_SUCCESS; 什么意思?

return EXIT_SUCCESS; 是 C 和 C++ 中主函数 main() 中常见的返回语句之一。它表示程序正常退出,并返回给操作系统一个表示成功的状态码。

具体来说,EXIT_SUCCESS 是一个在标准库 <cstdlib> 中定义的宏,它代表了程序成功执行的状态码。通常,EXIT_SUCCESS 的值是0。返回0通常表示程序成功执行,没有发生错误。

相对应的,如果程序出现了错误或者异常情况,可以使用 return EXIT_FAILURE; 来返回一个表示失败的状态码,EXIT_FAILURE 也是一个在 <cstdlib> 中定义的宏,其值通常是非0。

例如,在 main() 函数中,你可以这样使用这两个宏:

#include <cstdlib>

int main() {
    // 一些代码...

    if (/* 检测到错误 */) {
        return EXIT_FAILURE; // 返回表示失败的状态码
    }

    // 其他代码...

    return EXIT_SUCCESS; // 返回表示成功的状态码
}

这样做的好处是,操作系统可以根据返回的状态码判断程序的执行结果,进而进行后续的处理。

 

7. system("pause");又是什么意思?

system("pause"); 是一个在 C 和 C++ 程序中常见的用于暂停程序执行的语句。

具体来说,system() 函数是 C 和 C++ 标准库 <cstdlib> 中提供的函数,它允许程序执行外部命令。而 "pause" 是一个用于在命令行窗口中暂停程序执行的命令。

在 Windows 操作系统中,当命令行窗口执行 "pause" 命令时,会等待用户按下任意键后继续执行,这样就可以使得程序执行暂停,并且等待用户的输入。

因此,system("pause"); 这一语句的作用就是让程序暂停执行,等待用户按下任意键后继续执行。这在调试程序时非常有用,因为它可以让程序执行到一定程度后暂停,以便查看程序执行的结果或者调试程序。不过需要注意的是,在实际的应用中,最好避免在程序中使用 system("pause"); 这样的语句,因为它会使得程序的移植性和可维护性变差。

 
8. C++中二维数组内存也有要求像一维数组一样,一定是连续的么?

在 C++ 中,二维数组的内存布局与一维数组类似,它们都要求元素在内存中是连续存储的。也就是说,二维数组的每一行都是存储在连续的内存块中,并且各行之间也是连续的。

考虑一个二维数组 arr[row][col],其中 row 表示行数,col 表示列数。在内存中,数组的元素按照行优先的顺序连续存储。也就是说,数组的第一行元素依次存储在内存中,然后是第二行元素,以此类推。

这种内存布局的好处是,可以通过简单的索引运算来访问数组的元素,而不需要进行复杂的计算。例如,对于二维数组 arr,要访问第 i 行第 j 列的元素,可以使用 arr[i][j] 来直接访问,而不需要手动计算地址。

总的来说,C++ 中的二维数组也要求元素在内存中是连续存储的,这样可以简化数组的访问操作,并且有利于性能优化。

 
 
9. <<符号又有什么作用?为什么不用>>?

在 C++ 中,<< 是一个输出流操作符,通常用于将数据输出到流中,比如输出到控制台或文件等。

cout << "*p = " << *p << endl; 这行代码中,<< 连续使用了两次,每次表示将右侧的内容输出到左侧的流中。也就是说,cout 是一个输出流,"*p = "*p 是要输出的内容,endl 是表示换行的操作符。这样的连续输出可以将多个数据连续输出到流中,从而实现一次输出多个内容。

关于为什么不用 >>,这是因为在 C++ 中,>> 是输入流操作符,用于从流中提取数据,而不是输出到流中。因此,在输出数据时应该使用 << 而不是 >>

 
 
10. endl有何作用?

endl 是 C++ 标准库中的一个输出控制符,它的作用是结束当前行,并将缓冲区中的内容刷新到输出设备(比如屏幕)。具体来说,endl 执行以下两个操作:

  1. 将输出流中的缓冲内容写入到输出设备中,确保输出立即显示在屏幕上(而不是留在缓冲区中等待进一步的输出)。
  2. 在输出流中插入一个换行符,将输出移到下一行。

举个例子,假设有如下代码:

#include <iostream>

int main() {
    std::cout << "Hello, ";
    std::cout << "world!" << std::endl;
    return 0;
}

在这个例子中,第一条 std::cout 语句输出了 "Hello, ",然后使用 << 连接了第二条 std::cout 语句输出 "world!",最后跟着 std::endl,这样可以确保 "Hello, world!" 被输出到屏幕上,并且换行到下一行。

总之,endl 在输出流中起到结束当前行、刷新缓冲区、插入换行符的作用,使得输出的内容能够立即显示在屏幕上,并且换行到下一行。【它的效果相当于完成一次换行并清空缓冲区。

 
最后,举例说明有无endl带来的实际差别:
#include <iostream>

int main() {
    // 使用 std::endl
    std::cout << "使用 std::endl:" << std::endl;
    std::cout << "Hello, ";
    std::cout << "world!" << std::endl;
    std::cout << "This is a new line." << std::endl;

    // 不使用 std::endl
    std::cout << "\n不使用 std::endl:" << std::endl;
    std::cout << "Hello, ";
    std::cout << "world!";
    std::cout << "This is a new line.";

    return 0;
}


# 输出的差异
使用 std::endl:
Hello, world!
This is a new line.

不使用 std::endl:
Hello, world!This is a new line.

 

 
11. “缓冲”(buffering)和“缓存”(caching)的概念和区别。
  1. 缓冲(Buffering):

    • 缓冲是一种临时存储数据的机制,用于暂时保存即将被处理的数据。
    • 在输入/输出中,缓冲被用来临时存储数据,以便等待处理或传输到其他地方。
    • 输入/输出缓冲区在操作系统和应用程序中都有广泛的应用,它们可以减少对数据的多次访问和传输,提高系统性能。
    • 例如,当你使用 std::cout 输出一些内容时,数据首先会被写入输出缓冲区,然后根据操作系统的策略进行输出,这样可以减少频繁的 I/O 操作,提高效率。
  2. 缓存(Caching):

    • 缓存是一种高速临时存储数据的机制,用于存储最近访问的数据,以便在将来更快地访问。
    • 缓存通常用于存储最频繁访问的数据,以减少对慢速存储介质(比如硬盘)的访问次数,从而提高系统的响应速度。
    • 缓存可以存在于多个层次,比如 CPU 缓存、磁盘缓存等。
    • 例如,当你访问一个网页时,浏览器会将一些静态资源(比如图片、脚本文件)缓存在本地,以便在下次访问相同页面时能够更快地加载这些资源,提高用户体验。

总之,缓冲和缓存是两种不同的存储机制,用于不同的目的。缓冲用于临时存储数据以便等待处理或传输,而缓存则用于存储最近访问的数据以提高访问速度。

两者主要区别如下:

缓冲区(Buffer):

  1. 设计目的:

    • 缓冲区的设计目的是为了平衡数据传输速度和处理速度之间的差异,提高数据传输的效率。
    • 主要用于临时存储即将进行输入或输出的数据,以便提供更高效的数据传输和处理。
  2. 工作原理:

    • 缓冲区存储的数据通常是一次性的,一旦数据被传输或处理完成,就会被清空或覆盖。
    • 缓冲区的大小通常由系统或应用程序根据需求进行动态调整,以保证数据传输和处理的平衡。
  3. 应用场景:

    • 在输入/输出操作中,通常使用缓冲区来临时存储待处理的数据,以提高数据传输的效率。
    • 例如,在文件读写、网络通信等场景中,缓冲区被广泛应用于存储待传输的数据。

缓存区(Cache):

  1. 设计目的:

    • 缓存区的设计目的是为了提高数据访问速度,减少对慢速存储介质的访问次数,从而提高系统的响应速度。
    • 主要用于存储最近频繁访问的数据,以利用局部性原理提高数据访问的效率。
  2. 工作原理:

    • 缓存区存储的数据是根据访问模式和替换策略来确定的,通常是通过缓存管理算法来实现数据的替换和更新。
    • 缓存区的大小通常是固定的,并且可以存储较长时间未访问的数据。
  3. 应用场景:

    • 在计算机体系结构中,缓存被广泛应用于存储最近频繁访问的指令和数据,以提高 CPU 访问内存的速度。
    • 在应用程序中,缓存被用于存储常用的数据或计算结果,以减少重复计算和提高响应速度。

综上所述,缓冲区和缓存区有着不同的设计目的、工作原理和应用场景,它们在计算机系统中发挥着不同的作用。

 

从上述回答有2个新问题。

1. 缓冲区的设计目的是为了平衡数据传输速度和处理速度之间的差异,提高数据传输的效率。?请举例说明怎么平衡的?

答: 缓冲区的设计目的是为了平衡数据传输速度和处理速度之间的差异,提高数据传输的效率。

缓冲区的设计目的是为了解决数据传输速度和数据处理速度之间的不匹配问题。例如,在网络通信中,发送方和接收方的数据传输速度可能不一致。如果没有缓冲区,当发送方发送数据的速度快于接收方处理数据的速度时,接收方可能会丢失部分数据。相反,如果接收方的处理速度快于发送方的发送速度,接收方将不得不频繁地等待数据的到达,导致资源的浪费。

通过使用缓冲区,可以平衡这种速度差异。发送方将数据写入缓冲区,然后由缓冲区逐个将数据传输到接收方,以适应接收方的处理速度。这样可以确保数据在传输过程中不会丢失,并且可以充分利用传输链路,提高数据传输的效率。

 

2.缓冲区存储的数据通常是一次性的,一旦数据被传输或处理完成,就会被清空或覆盖。?也就是说一旦数据被传输和处理完成,该缓冲区就不存在了?

答:缓冲区存储的数据通常是一次性的,一旦数据被传输或处理完成,就会被清空或覆盖。

这句话的意思是,缓冲区中存储的数据通常是临时性的,只用于一次传输或处理过程。一旦数据被成功传输或处理完成,缓冲区中的数据就会被清空或覆盖,以便为下一次传输或处理准备空间。换句话说,并不是缓冲区本身被清空或销毁,而是缓冲区中的数据被清空或覆盖。

举个例子,考虑一个用于网络通信的发送缓冲区。当发送方将数据写入缓冲区后,数据被逐个发送到网络,直到所有数据都被成功发送完成。在这个过程中,缓冲区中的数据逐渐被传输出去,直到缓冲区中不再有待发送的数据为止。这时候,缓冲区中的数据就被清空或覆盖,以便为下一次数据传输准备空间。

12. 内存编号0 ~255为系统占用内存,不允许用户访问,那么这些编号的内存是干啥的?

在大多数操作系统中,系统会保留一部分内存用于系统自身的运行和管理。这些内存地址范围通常是 0 到 255(或者更大),被保留给操作系统内核和系统驱动程序使用。这些内存用途包括但不限于以下几点:

  1. 内核数据结构:操作系统内核需要存储大量的数据结构来管理系统资源,如进程控制块、内存管理表、文件系统信息等。这些数据结构通常存储在内核占用的内存地址中。

  2. 中断向量表:中断向量表是用于处理中断请求的数据结构,其中包含了每个中断号对应的中断服务程序的入口地址。这些信息必须位于系统占用内存中,以确保在任何时候都能快速响应和处理中断请求。

  3. 系统堆栈:每个运行在系统中的进程都有自己的堆栈空间,用于存储函数调用的局部变量、函数参数、返回地址等信息。操作系统需要为每个进程分配堆栈空间,并且这些堆栈空间通常被放置在系统占用的内存中。

  4. 设备映射:一些系统资源如 I/O 端口、内存映射设备等可能会被映射到系统占用的内存地址中,以便操作系统能够直接访问和控制这些设备。

  5. 内核代码段:操作系统内核的代码通常也会占用一部分内存地址空间,这些代码用于执行操作系统的各种功能和服务。

总之,系统占用的内存地址空间用于存储操作系统内核、驱动程序、中断处理程序以及一些重要的系统数据结构和设备映射信息,以保证操作系统能够正常运行并提供各种功能和服务。这些内存地址通常是受保护的,用户程序无法直接访问或修改。

 
12.1 难道就真没办法通过代码访问?

在一些特殊的情况下,可能会有一些方法可以访问系统占用的内存地址,但这通常需要特权或者操作系统提供的特定接口。普通的用户程序通常是无法直接访问系统占用的内存地址的。

下面列举一些可能的情况:

  1. 特权级别:一些操作系统提供了特权级别较高的进程或特权操作(比如内核模式),可以通过特定的指令或者系统调用来访问系统占用的内存地址。但这通常需要系统管理员权限或者特定的授权。

  2. 调试工具:调试工具如调试器或者监视器可能可以提供对系统占用内存地址的访问,以便调试系统软件或者驱动程序。但这同样需要相应的权限和特权。

  3. 操作系统提供的接口:一些操作系统可能会提供一些接口或者机制,允许用户程序间接地访问系统占用的内存地址。这可能会涉及到系统调用或者特定的用户态 API。

总的来说,直接访问系统占用的内存地址通常是不被允许的,并且在现代操作系统中也是受到严格限制的。这样做可能会导致系统的不稳定或者安全问题。因此,除非有特殊的需求和权限,否则普通的用户程序是无法直接访问系统占用的内存地址的。

 
 13. 野指针的理解
//示例2
int main()
{
    //指针变量p指向内存地址编号为0x1100的空间
    int* p = (int*)0x1100;
    //访问野指针报错
    cout << *p << endl;

    system("pause");
    return 0;
}
为什么上述指针就是野指针,野有什么标准来判断?
答:其实很好判断,0x1100是我随便向内存中指向的一个编号,这个编号里面的数我没有权力操纵它,这个0x1100的内存空间也不是我申请的。所以就没有权利操作0x1100这块内存,因为没有授权,这就是野指针的“野”的理解。
 
 
14. 为什么如下函数的形参写的分明是指针,但是命名却叫做地址传递?
//地址传递
void swap2(int * p1, int *p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}

这个问题涉及到了编程中的一些术语和惯例。在 C/C++ 中,函数的参数通常被称为形参(formal parameter),而函数调用时传递给形参的值则称为实参(actual argument)

在你提供的代码中,形参 p1p2 确实是指针类型,因为它们声明为 int* 类型指针变量存储的是内存地址,因此函数调用时传递给 swap2 函数的实参应该是变量的内存地址

虽然形参是指针类型,但是将这种传递方式称为 "地址传递" 是因为函数内部通过指针来操作实际变量的值。当你传递一个变量的地址给函数时,你实际上是将该变量的地址传递给了函数,使得函数能够直接修改该地址处的值。因此,这种传递方式被称为 "地址传递"。

总的来说,虽然形参是指针类型,但是命名为 "地址传递" 是为了更清晰地表达函数的作用:通过操作内存地址来修改实际变量的值。

 

15. 如下代码中报错的理解

void swap2(int* p1, int* p2)
{
    int temp = *p1;
    *p1 = *p2;
    *p2 = &temp  # 为什么这样写,这一行会报错?

在代码中,p2 是一个指针,而&temp 是一个指向 temp 变量地址的指针。这两者的类型不匹配,导致赋值操作报错。

具体来说,*p2 表示 p2 指针所指向的变量的值,而&temp 表示 temp 变量的地址。这两者的类型不同,一个是 int 类型的值,一个是 int* 类型的指针。

要修正这个问题,你应该将 *p2 的值修改为 temp 的值,而不是将 *p2 的指针修改为 temp 的地址。正确的代码应该是:

void swap2(int* p1, int* p2)
{
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp; // 将 p2 指向的变量的值修改为 temp 的值
}

&temp 表示取 temp 变量的地址,它的类型是指向 temp 变量地址的指针,而不是指向 temp 变量本身的指针。在C/C++中,&temp 表达式返回的是 temp 变量的地址,即一个指针类型(int*)。因此,&temp 是一个指向 temp 变量地址的指针,而不是一个指向 temp 变量本身的指针。

在你提供的代码中,*p2 = &temp; 这行代码是错误的,因为 *p2 是一个 int 类型的变量,而 &temp 是一个 int* 类型的指针,类型不匹配。正确的做法是直接将 temp 的值赋给 *p2,而不是将 &temp(地址值)赋给 *p2。因此,应该写成 *p2 = temp;

 

 16. 关于指针类型, 如下报错的深入理解。
#include <iostream>
using namespace std;

struct student
{
    string name;
    int age;
    int score;
};

int main()
{
    struct student stu = {"张三", 18, 100};
    int* p = &stu;  // 这一行是错误的,编译器不会通过

    p->score = 80;
    cout << "姓名: " << p->name << " 年龄: " << p->age << " 得分: " << p->score << endl;

    system("pause");
    return 0;
}

我在遇见这个问题时,之前的本能理解是:"不管是结构体变量 还是int变量,内存地址都是整形的,那既然如此,为什么不能用int* p 来接收 &stu呢?"

 我这么思考固然有一定的道理,但是却忽略了指针类型这个重要概念。

虽然结构体变量的地址和 int 变量的地址都是内存中的整数,但是在 C++ 中,指针的类型是很重要的,它告诉编译器如何解释该地址指向的内存内容

在这段代码中,p 是一个 int* 类型的指针,表示它指向的是一个 int 类型的变量或者一片连续的 int 类型的数据。而 stu 是一个结构体变量,它的类型是 student,它的地址并不是一个 int 类型的地址,而是一个 student 类型的地址。

因此,尝试将一个 student 类型的地址赋给一个 int* 类型的指针是不合法的,因为这相当于将不同类型的指针进行了赋值,这在 C++ 中是不允许的。编译器会提示类型不匹配的错误。

要修正这个问题,应该使用 student* 类型的指针来接收 stu 的地址,这样就能正确地访问结构体 stu 中的成员了。

 

在 C++ 中,指针的类型决定了编译器在访问该地址时如何解释内存内容。虽然一个地址(内存位置)可以被不同类型的指针指向,但不同类型的指针会导致编译器对该地址进行不同的解释。

 举例如下

例如,假设有一个地址 0x1234,它可以被 int* 类型的指针和 char* 类型的指针同时指向。但是,当你使用 int* 指针去访问该地址时,编译器会将其解释为一个 int 类型的值;而当你使用 char* 指针去访问该地址时,编译器会将其解释为一个 char 类型的值。因此,指针的类型告诉编译器如何正确地解释内存内容。

这种行为可以帮助程序员在编程过程中正确地操作数据,同时也增强了代码的可读性和可维护性。

 

posted @ 2024-02-27 13:57  AlphaGeek  阅读(14)  评论(0)    收藏  举报