Geekiter/geekiter.github.io

240328Note: em - c&data structure

Opened this issue · 0 comments

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

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

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

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

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

#define MIN(A, B) ((A) <= (B) ? (A) : (B))
  • 要用三重条件操作符,在宏中要小心的把参数用括号括起来,并且宏也要用括号括起来,防止替换出现错误
  • 注意,如果写least = MIN(*p++, b)这句话会产生副作用,将*p++代入宏体,指针p会做2次自增操作

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;
  • 由于是寄存器地址,所以需要将其强制类型转换为volatile unsigned long *

在 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

解读:

  • 注意预处理将为你计算常数表达式的值,并且整个宏体要用括号括起来
  • 注意这个表达式将使一个16位机的整形数溢出,因此要用到无符号长整型符号UL,告诉编译器这个常数是无符号长整型数

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

  • static修饰局部变量时:改变了其存储位置,存储在静态区;同时改变了其生命周期,为整个源程序,因此它只被初始化一次,若没显式初始化则自动初始化为0.
  • static修饰全局变量时,改变了其作用域,只可以被文件内所用函数访问
  • 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()呢

  • sizeof是用来计算变量、数据类型所占内存的字节数。sizeof数组名得到数组所占字节数,sizeof(字符串指针名)得到指针所占字节数
  • strlen函数用来测试字符串所占字节数,不包括结束字符\0,strlen(字符数组名)得到字符串所占字节数,strlen(字符串指针名)得到字符串所占字节数

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

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

  • 注意引用不能初始化,如extern var,而不能少extern var = 0;
  • 另外,函数extern类型的,表明整个程序可见的,加不加都一样

1.18 extern “C”的作用?

  • 在c++代码中调用c的函数,用法:extern "C" {C函数库头文件/函数声明}

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

  • 用来定义自动局部变量,自动局部变量在进入声明该变量的语句块时被建立,退出语句块时被注销,仅在语句块内部使用
  • 其实普通局部变量就是自动局部变量,只是省略了auto这一关键字

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

  • 编译器会将register修饰的变量尽可能地放在CPU寄存器中,以加快其存取速度,一般用于频繁使用的变量。
  • register变量可能不存放在内存中,所以不能用&来获取该变量的地址;只有局部变量和形参可以作为register变量;寄存器数量有限,不能定义过多register变量

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

  • volatile在编译阶段,extern在链接阶段
  • C语言编译过程分别为预处理、编译、汇编、链接

1.22 const与#define的异同

  • 异:const有数据类型,编译器可以做静态类型检查;而宏定义没有类型,可能会导致类型出错
  • 同:都可以用来定义常数