177. #include <filename>
和 #include "filname.h"
有什么区别?
对于 #include <filename>
, 编译器从标准库路径开始搜索 filename.h。
对于 #include "filename.h
,编译器从用户的工作路径开始搜索filename.h。
解释 :
主要是关于编译器搜索头文件路径的优先级和顺序。
#include <filename>
- 搜索路径 :编译器首先从标准库的包含路径(例如,编译器安装目录下的标准头文件目录)中开始搜索
filename
。 - 用途:通常用于包含标准库头文件或第三方库的头文件,这些文件通常位于系统或编译器指定的标准路径中。
#include "filename"
- 搜索路径 :编译器首先从当前的工作目录或源文件所在的目录开始搜索
filename
。 - 用途:通常用于包含用户自定义的头文件,这些文件通常与源文件位于同一目录或项目的相对目录中。
- 后备搜索路径 :如果在当前工作目录或源文件所在的目录中找不到
filename
,编译器将退而在标准库路径中继续搜索(类似于#include <filename>
的路径)。
示例
假设我们有以下文件结构:
c
project/
│
├── src/
│ ├── main.cpp
│ └── custom.h
└── include/
└── library.h
main.cpp:
c
#include "custom.h" // 编译器首先在 src/ 目录中搜索 custom.h
#include <library.h> // 编译器直接在 include/ 目录(标准路径之一)中搜索 library.h
#include "filename"
示例
- src/main.cpp:
c
#include "custom.h" // 将找到 src/custom.h
编译器会首先在 src
目录中搜索 custom.h
,因为它是相对路径。
#include <filename>
示例
- src/main.cpp:
c
#include <library.h> // 将找到 include/library.h
编译器会在标准库路径中搜索 library.h
,通常配置中的 include
目录或编译器的默认头文件目录。
178. 处理器标识#error的目的是什么?
答:
编译时输出一条错误信息,并中止继续编译。
解释:
处理器指令 #error
在预处理阶段被用来生成编译错误信息,其主要目的是在特定条件不符合时中止编译过程,并提示开发者相关信息。具体来说,#error
指令的作用包括:
- 生成编译错误 :
- 当预处理器遇到
#error
指令时,会立即生成一条错误消息,并将其作为编译错误输出。这条错误消息可以由开发者自定义,通常用来指示特定条件或预期行为没有被满足。
- 当预处理器遇到
- 中止编译过程 :
- 由于
#error
指令生成的错误是编译时错误,编译器在遇到这条错误消息时将会停止继续编译。这有助于避免在不符合预期条件时生成无效的可执行文件或库文件。
- 由于
示例
以下是一个简单的示例,演示了如何使用 #error
指令来检查预期条件:
c
#ifndef DEBUG_MODE
#error "DEBUG_MODE is not defined. Please define DEBUG_MODE."
#endif
int main() {
// 一些代码
return 0;
}
在这个示例中,如果 DEBUG_MODE
宏未被定义,预处理器会生成一条错误消息并停止编译,错误消息为 "DEBUG_MODE is not defined. Please define DEBUG_MODE."
。
应用场景
- 条件编译中的验证 :可以在代码中使用
#error
来确保特定的预处理条件已经被定义或者符合预期。 - 特定平台或环境的验证 :在跨平台开发中,可以使用
#error
来确保代码在目标平台上能够正常编译和运行。 - 版本控制 :在软件版本控制中,
#error
可以用来确保所需的库或模块版本被正确包含或定义。
总之,#error
指令是预处理阶段中的一种工具,用来在编译前验证特定条件的合法性,一旦条件不符合,即可生成错误消息并中止编译过程,有助于提前发现和解决潜在的编译时问题。
179. #if!defined(AFX_..._HADE_H) #define(AFX_..._HADE_H) ...... #endif
的作用?
防止该头文件被重复引用。
解释:
c
#if !defined(AFX_..._HADE_H)
#define AFX_..._HADE_H
// 头文件的具体内容在这里
#endif
#if !defined(AFX_..._HADE_H)
#if !defined(...)
是预处理器的条件编译指令。defined(...)
是一个预处理器操作符,用于检查某个宏是否已经被定义。
在这里,AFX_..._HADE_H
是一个自定义的宏名(通常是头文件名称的标识符),通过 #if !defined(...)
来检查该宏是否未定义 。如果 AFX_..._HADE_H
尚未被定义,则条件成立,预处理器会执行下面的代码块,否则会跳过。
#define AFX_..._HADE_H
- 如果
AFX_..._HADE_H
未定义,预处理器会执行#define AFX_..._HADE_H
指令来定义这个宏。 - 这样做的目的是为了标记当前头文件已经被引用,避免再次被重复引用。
头文件的具体内容
- 在
#define AFX_..._HADE_H
和#endif
之间,是头文件的实际内容。这些代码会在第一次引用时被预处理器包含到源文件中。
#endif
#endif
表示条件编译的结束。它和#if
配对,用来结束条件编译的代码块。- 在这里,它结束了
#if !defined(AFX_..._HADE_H)
条件成立时执行的代码块。
作用和理解
-
防止重复引用 :通过上述方式,只有在第一次引用头文件时,
AFX_..._HADE_H
宏才会被定义。在后续的引用中,由于该宏已经被定义,预处理器会跳过#if !defined(...)
的条件分支,从而避免重复包含头文件的内容,提高编译效率并避免编译错误。 -
唯一标识符 :
AFX_..._HADE_H
是一个唯一的标识符,通常会包含头文件名称的一部分,以确保在整个项目中的唯一性。 -
常见应用:这种技术被广泛用于C和C++的头文件中,特别是在大型项目中,用来管理复杂的依赖关系和避免重复定义问题。
综上所述,这种使用方式确保了头文件只被编译一次,同时通过宏定义来标记头文件的引用状态,是 C 和 C++ 中常见且重要的编程实践之一。
180. 在定义一个宏的时候应该注意什么?
定义部分的每个形参和整个表达式都必须用括号括起来,以避免不可预料的错误发生。
解释:
- 使用括号括起每个形参和整个表达式 :
- 每个宏形参和整个宏表达式都应该用括号括起来,这可以防止由于操作符优先级导致的意外行为。例如:
#define SQUARE(x) ((x) * (x))
这样定义可以确保在展开宏时,参数x
的值不受周围环境的影响,同时也避免了乘法操作的优先级问题。
- 每个宏形参和整个宏表达式都应该用括号括起来,这可以防止由于操作符优先级导致的意外行为。例如:
- 注意宏展开的副作用 :
- 宏展开是简单的文本替换,可能会导致意料之外的副作用,尤其是对于带有副作用的表达式(例如
++
、--
、函数调用等)。确保宏的定义不会影响代码的可读性和功能的预期行为。
- 宏展开是简单的文本替换,可能会导致意料之外的副作用,尤其是对于带有副作用的表达式(例如
- 选择符合语义的命名 :
- 宏的命名应该有意义,并符合语言规范。避免使用已有的标准库函数或变量名,以及避免与其他宏冲突。
- 考虑可读性和维护性 :
- 宏的内容应该简洁明了,易于理解和维护。过于复杂或者不必要的宏定义可能会降低代码的可读性和可维护性。
- 避免过度使用宏 :
- 宏是一种强大的工具,但过度使用宏可能会导致代码难以调试和理解。在合适的情况下,应优先考虑使用函数或者内联函数代替宏。
- 宏定义后不带分号 :
- 宏定义通常不应该以分号结尾,因为在展开时会导致意外的语法错误。
示例
考虑下面这个简单的宏定义的例子:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
这个宏定义了一个获取两个数中较大值的宏。注意到它:
- 每个形参和整个表达式都用括号括起来,避免了因操作符优先级引起的问题。
- 定义简洁明了,功能清晰。
通过这些注意事项,可以有效地定义宏,避免潜在的问题,并使代码更加健壮和可维护。
181. 数组在做函数实参的时候会转变为什么类型?
数组在做函数实参的时候会变成指针类型。
182. 系统会自动打开和关闭的三个标准头文件是什么?
- 标准输入------键盘------stdin
- 标准输出------显示器------stdout
- 标准出错显示------显示器------stderr
183. 在Win32下,char、int、float、double各占多少位?
- char 占 8 位
- int 占 32 位
- float 占 32 位
- double 占 64 位
解释:
在 Win32 下,通常的数据类型 char
、int
、float
、double
各占用的位数如下所示:
- char :
- 占用位数:8位
- 字节大小:1字节(8位)
- 取值范围:-128 到 127 (有符号)或 0 到 255 (无符号)
- int :
- 占用位数:32位
- 字节大小:4字节(32位)
- 取值范围:-2,147,483,648 到 2,147,483,647 (有符号)或 0 到 4,294,967,295 (无符号)
- float :
- 占用位数:32位
- 字节大小:4字节(32位)
- 单精度浮点数,通常表示 IEEE 754 标准的单精度浮点数格式,范围和精度有限,但通常足够大多数应用。
- double :
- 占用位数:64位
- 字节大小:8字节(64位)
- 双精度浮点数,通常表示 IEEE 754 标准的双精度浮点数格式,提供更大的范围和更高的精度。
注意事项
- 这些数据类型的大小和取值范围在不同的编译器和系统环境下可能会有所不同,但在大多数现代的 Win32 系统中,上述描述是普遍适用的。
- 数据类型的确切大小和范围可以使用
sizeof
运算符来确定,例如sizeof(char)
、sizeof(int)
、sizeof(float)
、sizeof(double)
。 - 在不同的操作系统、编译器以及特定的编译器标志(如编译为32位或64位应用程序)下,这些数据类型的大小可能会有所不同,因此在具体开发中,建议通过查阅编译器和操作系统的文档来确认具体的数据类型大小和范围。
184. strcpy() 和 memcpy()的区别?
两者都可以用来拷贝字符串,strcpy()拷贝以'\0'结束 ,但memcpy必须指定拷贝的长度。
解释 :
strcpy()
和 memcpy()
都是 C 语言中的标准库函数,用于复制数据,但它们的用途和行为有明显的区别。理解这两个函数的不同在于它们的应用场景和数据处理方式。下面我们详细讨论它们的区别。
strcpy()
strcpy()
函数专门用于复制以空字符('\0'
)结尾的字符串。
特点:
- 目的 :用于复制由
'\0'
结尾的字符串。 - 终止条件 :在复制过程中,它会一直复制字符,直到遇到一个
'\0'
字符,然后再把这个'\0'
字符也复制到目标位置。 - 目标与源的类型 :目标和源需要都是
char
类型的字符串。
示例:
c
#include <cstring>
int main() {
const char* src = "Hello, World!";
char dest[20]; // 确保目标数组足够大
strcpy(dest, src); // 复制 src 到 dest
printf("%s\n", dest); // 输出: Hello, World!
return 0;
}
在这个例子中,strcpy
将 "Hello, World!"
的内容复制到 dest
数组中,并确保 dest
以 '\0'
结尾。
memcpy()
memcpy()
函数是通用的内存复制函数,不考虑数据内容。
特点:
- 目的:用于复制任意类型的数据。
- 终止条件 :根据传递的字节数进行复制,而不是通过检测
'\0'
。 - 目标与源的类型:可以是任何类型的内存块,只要这两个内存块大小不重叠。
示例:
c
#include <cstring>
int main() {
int src[5] = {1, 2, 3, 4, 5};
int dest[5];
memcpy(dest, src, 5 * sizeof(int)); // 复制 src 到 dest,每个元素占 sizeof(int) 个字节
for(int i = 0; i < 5; ++i) {
printf("%d ", dest[i]); // 输出: 1 2 3 4 5
}
return 0;
}
在这个例子中,memcpy
将 src
数组的内容逐字节复制到 dest
数组中。
主要区别总结:
-
类型与语义:
strcpy
专门用于复制以'\0'
结尾的字符串,目标和源都是char
数组。memcpy
用于复制任意类型的内存块,不关心数据类型和内容。
-
复制方式:
strcpy
依赖于源字符串中的'\0'
终止符,停止复制并复制终止符。memcpy
根据指定的字节数复制,不会自动添加终止符。
-
应用场景:
- 使用
strcpy
复制 C 字符串时无需显式指定长度,只要确保目标数组有足够空间保存源字符串以及终止符。 - 使用
memcpy
复制任意数据类型,需要显式地指定复制的字节数。
- 使用
形象化理解的例子:
- 用
strcpy
复制:就像你在复制一段文字,只有在你遇到句号(终止符)时你才知道复制结束。 - 用
memcpy
复制:就像你在搬家,你要搬 10 个箱子,你只关心数量,不关心箱子里面是什么。
注意事项:
- 越界问题 :使用
strcpy
时如果目标数组空间不足以容纳源字符串,可能会导致越界错误,引发未定义行为。 - 重叠区域 :
memcpy
不适用于内存重叠区域的复制,如果源和目标内存区域重叠,应使用memmove
。
185. 说明define和const在语法和含义上有什么不同?
# define
是C语言中定义符号常量 的方法,符号常量只是用来表达一个值,在编译阶段符号就被值替换了,他没有类型const
是C++语法中定义常变量 的方法,常变量具有变量特性,它具有类型 ,内存中存在以它命名的存储单元,可以用sizeof测出长度
186. 说出字符常量和字符串常量的区别,并使用sizeof计算有什么不同?
字符常量是指单个字符,字符串常量以'\0'结束,使用运算符sizeof计算多占一字节的存储空间。
解释:
字符常量
- 字符常量 是单个字符,用单引号
' '
括起来,例如'a'
、'1'
、'!'
。 - 存储空间:字符常量通常占用一个字节(8位),但是也可能因为编码格式原因占用更多的空间(例如UTF-8编码中的某些字符可能占用多个字节)。
字符串常量
- 字符串常量 是由双引号
" "
括起来的一些字符,例如"hello"
,"world"
,"abc123!"
。 - 字符串终止符 :字符串常量以
'\0'
(空字符)结束,这个空字符也称为字符串的终止符。
内存占用和 sizeof 的用法
c
#include <iostream>
using namespace std;
int main() {
char c = 'a';
char str[] = "hello";
cout << "Size of character constant 'a': " << sizeof(c) << " byte(s)" << endl; c
out << "Size of string constant \"hello\": " << sizeof(str) << " byte(s)" << endl;
return 0;
}
- 字符常量的大小 :
sizeof(c)
返回字符变量c
的大小。- 输出通常是
1
,因为字符常量在大多数系统中占用一个字节。
- 字符串常量的大小 :
sizeof(str)
返回字符数组str
的大小。- 这里
str
包含"hello"
,但是要注意它的实际存储方式,包含一个终止符'\0'
。 sizeof(str)
的结果是6
,因为"hello"
是5个字符,加上一个终止符'\0'
,总共6个字符。
187. 简述全局变量的优缺点
全局变量也称为外部变量,他是在函数外部定义的变量,它属于一个源程序文件,他保存上次被修改后的值,便于数据共享,但不方便管理,容易引起意想不到的错误。
解释 :
全局变量是指在程序中声明的具有全局作用域的变量,即其值可以被整个程序中的任何函数访问和修改。全局变量虽然有其特定的应用场景,但也有明显的优缺点。以下是对全局变量的优缺点的简述:
优点
-
方便数据共享 :
全局变量可以在整个程序中共享使用,无需通过参数传递即可实现不同函数之间的数据共享。对于需要全局配置或状态信息的情况,全局变量是较为便捷的选择。
-
减少参数传递 :
当函数之间需要频繁传递相同的数据时,使用全局变量可以减少函数参数的传递,提高代码的可读性和简洁性。
-
数据持久性 :
全局变量在程序生命周期内始终存在并保持其值(除非显式改变),这适用于需要在多个函数调用中维持状态的数据。
缺点
-
命名空间污染 :
全局变量存在于全局命名空间,可能会与其他模块或第三方库的全局变量发生命名冲突,增加维护难度。为了避免冲突,通常需要对全局变量进行命名空间管理。
-
隐藏依赖关系 :
函数依赖于全局变量,这种依赖关系是隐式的,不容易从函数接口看出。使得代码难以理解和维护,特别是在代码规模较大时。
-
可维护性差 :
全局变量的修改可能在程序的任何地方发生,导致难以跟踪和调试问题。尤其是在团队开发时,不恰当地修改全局变量可能会引入难以察觉的bug。
-
难以重用 :
带有全局变量依赖的代码难以重用,因为它们之间存在隐式的耦合。函数的独立性降低,在非全局环境下重用变得困难。
-
线程安全问题 :
在多线程程序中,全局变量的使用需要特别小心。多个线程同时访问和修改全局变量,会导致数据竞争和不一致性,必须引入同步机制(如互斥锁)来确保线程安全,这增加了复杂性。
总结
虽然全局变量在特定情况下可以简化代码和方便数据共享,但在实际开发中,需要谨慎使用全局变量,尽量保持代码模块化和数据隔离。推荐使用局部变量、参数传递和返回值等方式来减少全局变量的使用,提升代码的可维护性和可读性。如果必须使用全局变量,应当对其进行命名空间管理,并在文档中详细说明其用途和修改记录。