1.预处理&关键字(22道)

1.1宏定义是在编译的哪个阶段被处理的?

宏定义是在预编译处理阶段被处理的

编译预处理:头文件包含:宏替换、条件编译、去除注释、添加行号

1.2写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。

#define MIN(A, B) ((A) <= (B) ? (A) : (B))

1.3已知数组table,用宏求数组元素个数。

#define COUNT(table) (sizeof(table) / sizeof(table[0]))

sizeof(table)得到数组长度,sizeof(table[0])得到数组元素长度,两者相除得到数组元素个数

1.4带参宏和函数的区别?

1.5内联函数的优缺点和适用场景是什么?


内联函数是一种C/C++编程语言特性,用于告诉编译器在调用函数时将函数体插入到调用点处,而不是按照常规的函数调用过程进行调用。这样可以减少函数调用的开销,提高程序的执行效率,尤其适用于简单且频繁调用的函数。

以下是一个简单的内联函数的例子:

#include <iostream>

// 内联函数定义
inline int square(int x) {
    return x * x;
}

int main() {
    int num = 5;
    int result = square(num); // 编译器会将此处的函数调用替换为函数体
    std::cout << "Square of " << num << " is: " << result << std::endl;
    return 0;
}

在这个例子中,square() 函数被声明为内联函数,并在 main() 函数中调用。编译器会将 square() 函数的函数体插入到 main() 函数中 square(num) 的位置,而不是按照传统的函数调用方式执行。

1.6关键字volatile的作用是什么?给出三个不同的例子。


volatile 是一个关键字,用于告诉编译器不要对变量进行优化,因为它的值可能随时会被外部因素改变,例如硬件设备、其他线程或信号处理程序等。因此,每次访问这样的变量时,都应该从内存中重新加载其值,而不是使用已经缓存的值。

以下是一个使用 volatile 关键字的简单示例:

#include <iostream>
#include <thread>

// 全局变量
volatile bool flag = false;

// 线程函数,不断修改flag的值
void toggle_flag() {
    while (true) {
        flag = !flag;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main() {
    // 启动一个线程,不断修改flag的值
    std::thread t(toggle_flag);

    // 主线程不断读取flag的值并打印
    while (true) {
        std::cout << "Flag is: " << flag << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    // 不会执行到这里,但是为了示例的完整性,加上join操作
    t.join();

    return 0;
}

在这个例子中,flag 被声明为 volatile bool 类型。主线程和子线程分别对 flag 进行读写操作。由于 flagvolatile 类型,编译器不会对其进行优化,因此每次读取 flag 的值时都会从内存中重新加载。这样可以确保主线程能够看到子线程对 flag 的修改。

1.7如何用C语言实现读写寄存器变量?

#define rBANKCONO (*(volatile unsigned long *) 0x48000004)
rBANKCONO = 0x12;

在 C/C++ 中,unsigned 是一种数据类型修饰符,用于声明无符号整数类型。无符号整数类型表示不能包含负数的整数,它们只能表示大于等于零的值。在内存中,无符号整数类型使用二进制表示,不包含符号位。

在你提供的代码中,unsigned 用于修饰 long 类型,表示 rBANKCONO 是一个无符号的长整型变量。而 volatile 关键字告诉编译器该变量的值可能会被外部因素改变,因此每次访问时都应该从内存中重新加载其值,而不是使用已经缓存的值。

#define rBANKCONO (*(volatile unsigned long *) 0x48000004)
rBANKCONO = 0x12;

在这段代码中,rBANKCONO 被定义为一个指向地址 0x48000004 的无符号长整型变量,并将其值设置为 0x12。这样的操作通常用于直接操作硬件寄存器或者内存映射设备。

1.8下面代码能不能编译通过?

#define c 3
c++

不能,3是常量,自增只能用于变量

1.9 在C语言中,凡是以#开头的都是预处理命令,同时预处理命令都是以#开头

正确

1.10 预处理器标识#error的作用是什么?

答案: 编译程序时,只要遇到error就会跳出一个编译错误
解读:当程序比较大时,往往有些宏定义是在外部指定的(如makefile),或是在系统头文件中指定的,当你不确定当前是否定义了XX时,可写如下预处理代码:

#ifdef XXX
#error "XXX has been defined"
#else 
...
#endif

这样,如果编译时出现错误,输出了XXX has been defined,表明宏XXX已经被定义了。

1.11 用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

解读:

1.12 关键字static的作用是什么?

1.13 说说下面const的作用

const int a; // a是一个整型常量
int const a; // a是一个整型常量

const int *a; // a是一个指向整型常量的指针变量
int *const a; // a是一个指向整型变量的指针常量
int const *  const a = &b; // a是一个指向整型常量的指针常量

char *strcpy(char *strDest, const char *strSrc); // 参数在函数内部不会被修改
const int strcmp(char *source, char *dest); // 函数的返回值不能被修改

1.14 一个参数既可以是const还可以volatile吗?一个指针可以是volatile吗?下面的函数有什么问题?

int square(volatile int *ptr){
	return *ptr * *ptr;
}

只读的状态寄存器,它是volatile,因为它可能被外部环境改变,但是它是const因为程序不应该试图去修改它。
一个例子是当一个中断服务子程序修改一个指向一个缓冲区的指针时。

int square(volatile int *ptr){
	int a, b;
	a = *ptr;
	b = *ptr;
	return a * b;
}

由于*ptr的值可能会被意想不到地改变,因此a和b可能不同。

long square(volatile int *ptr){
	int a;
	a = *ptr;
	return a * a;
}

1.15 关键字typedef 在c语言中频繁用以声明一个存在的数据类型的同义字。也可以用预处理器做类似的事。

#define dPS struct s *;
typeof struct s *tPS; //顺序、分好、#号

以上两种期刊的意图都是要定义dPS和tPS作为一个指向结构体s的指针。哪种方法更好,为什么?
typeof 更好

dPS p1, p2;
tPS p3, p4;

第一行代码扩展为struct s *p1 p2;

1.16 关键字sizeof的作用是什么?函数strlen()呢

1.17 关键字extern的作用是什么?

用于跨文件引用全局变量,即在本文件中引用一个已经在其他文件中定义的全局变量。

1.18 extern “C”的作用?

1.19 关键字auto的作用是什么?

1.20 关键字register的作用是什么?使用时需要注意什么?

1.21 C语言编译过程中,关键字volatile和extern分别在哪个阶段起作用?

1.22 const与#define的异同