《C和C++程序员面试秘笈》第2章 预处理、const、static与sizeof

《C和C++程序员面试秘笈》-董珊山海编著

在这里插入图片描述

1. 预处理的使用

#ifdef XXX
#else
#endif

2. #define

1、三目运算符( ? : ),能产生比if-else更优化的代码,并且书写上更加简洁明了
2、用#define定义的宏,如果是替换为数值类型,需要把参数括起来;如果只是简单的文本替换,如果不注意,使用时很容易引起歧义

3. #define 宏定义的使用

宏定义展开是在预处理时期,也就是在编译之前
如果参数需要加括号,加括号时,要加齐全

4. 宏参数的连接

1、使用#把宏参数变为一个字符串
   #define STR(s) #s
   STR(s)定义的是一个参数s表示的字符串
   当调用时,如:STR(vck),实际表示就是字符串“vck”
2、使用##把两个宏参数贴合在一起
   #define CONS(a,b) (int)(a##e##b)
   CONS(a,b)定义的是一个将参数a b按aeb连接起来的一个整型值
   当调用时,如:CONS(2,3),实际表示的就是整型值2e3,也就是十进制数2000

5.略
6.略

7. const的使用

常量指针  const修饰的是指针指向的内容,指针指向的内容为常量,不能改,指针指向可以改 
         const int* PointerToInt
指针常量  const修饰的是指针,指针指向不能更改,指针指向的内容可以改
         int* const PointerToInt

8. const与#define的特点与区别

#define 只是用来做文本替换的
const常量 有数据类型,编译器可以对其进行类型安全检查

9. C++中const有什么作用

1const定义常量,编译器可以对const常量进行数据静态类型安全检查
2const修饰函数形参,参数为用户自定义类型或者抽象数据类型时,“值传递”改为“const & 传递”,可以提高效率
3const修饰函数的返回值
4const修饰类的成员函数(函数的()后加const),任何不会修改数据成员的成员函数都应用const修饰

10. static有什么作用

1、全局的static变量(静态变量),可以被本文件内所有函数访问,不能被其他文件访问,被限制为一个本地的全局变量
2、局部的static变量(函数体内),只能被本函数调用

11. static全局变量与普通全局变量有什么区别

1static全局变量:只初始化一次;只能在定义该变量的源文件内使用
	普通全局变量:可被一个程序的各个源文件使用
2static局部变量:只初始化一次,下一次依据上一次的结果值
3static函数:在内存中只存在一份
	普通函数:在每个被调用中维持一份复制品

12. C++中类的静态成员

类的静态成员、静态方法不属于类的实例,而是属于类本身并在类的实例间共享;不占用类实例的空间(其中静态成员变量存在于静态存储区)。
在调用它们时应该用"类名::静态方法()"

13. sizeof计算普通变量所占空间大小

数组和指针的sizeof运算有细微的区别
	对“数组变量(数组名)sizeof运算得到的是数组占内存的总大小
	如果“数组变量(数组名)”被传入函数中做sizeof运算,则和指针的sizeof运算没有区别
	对于指针,无论何种类型的指针,其大小都是固定的;在32位WinNT平台下都是4

14. sizeof计算 类对象 / 结构体变量 所占空间大小(字节对齐)

C++的内存对齐方式取决于
	1、取 编译器默认对齐大小 与 对象中最大成员  两者中较小的一个
	2、现阶段,编译器默认对齐大小为8
	
一般而言满足3个准则
	1、结构体变量的首地址,能被其成员中最宽基本类型大小所整除
	2、结构体中每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍;
	   如有需要,编译器会在成员之间加上填充字节(internal adding)
	3、结构体的总大小为结构体成员中最宽基本类型大小的整数倍;
	   如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)

15. sizeof计算含有虚函数的类对象的空间大小

普通函数不占用类对象的内存
类中只要有虚函数,就会占用一个指针大小的内存
	原因是:系统多用了一个指针维护这个类的虚函数表
	并且注意,类中无论含有几个虚函数,都不会影响类的大小(因为只有一个虚函数指针,这个虚函数指针用来维护虚函数表)

16. sizeof计算"虚继承"的类对象的空间大小

虚继承时,编译器会为子类安插一个指向父类的指针
	一个虚继承,子类里有一个指向父类的指针
	多个虚继承,子类里有多个指向父类的指针(一一对应)

17. sizeof与strlen的区别

sizeof
	是操作符--计算所占内存空间大小 "\0"计算在内
	结果类型为size_t,即unsigned int类型
	类型做参数时,必须加“();变量名做参数时,可以不加“()”
	数组名直接传递给sizeof不退化为指针
	sizeof()可以用来定义数组维数
	
	sizeof计算字符串数组时,结果为字符串数组实际占用的字节数,"\0"计算在内
	
strlen
	是函数--计算字符串的长度 "\0"不计算在内
	只能用“char *”做参数,且必须以“\0”结尾
	数组名直接传递给strlen退化为指针
	
	strlen计算字符串数组时,结果为字符串实际占用的字节数,"\0"不计算在内

	如果要计算指针指向的字符串的长度,则一定要使用strlen
		char * ss = "0123456789";
		int a = sizeof(ss);//4
		int b = strlen(ss);//10

18. sizeof有哪些用途

1、查看某个类型的对象在内存中所占的字节数
2、动态分配对象的内存时,让系统知道要分配多少字节
3、涉及操作数的字节大小时,用sizeof代替常量计算

19. 使用strlen()函数代替sizeof计算字符串长度

1sizeof()不能计算字符串长度
2strlen()函数用于计算字符串长度

20. sizeof计算联合体的大小

unionstructclass内存对齐方式相同
union的大小
	1、成员中占用空间最大的一个成员的大小
	2、如果需要,进行内存对齐

#pragma pack(x)可以改变编译器的默认对齐大小

C++的内存对齐方式取决于
	1、取 编译器默认对齐大小 与 对象中最大成员  两者中较小的一个
	2、现阶段,编译器默认对齐大小为8

21. #pragma pack的作用

#pragma pack(x)可以改变编译器的默认对齐大小

22. 为什么要引入内联函数(inline)

宏函数
	仅仅只是做预处理器符号表中的简单替换
		不能进行参数有效性的检测
		不能享受C++编译器严格类型检查的好处
		返回值不能被强制转换为合适类型
		因此,它的使用就存在着一系列的隐患和局限性

C++中引入了类及类的访问控制
	涉及类的保护成员或私有成员时,就无法使用这种宏定义来实现(因为无法将this指针放在合适的位置)
	
inline推出的目的
	正是为了取代宏函数
	消除了它的缺点,同时又很好的继承了它的优点

23. 为什么inline能很好的取代宏函数

inline定义的内联函数
	1、函数的代码被放入符号表中,在使用时直接进行替换(像宏一样展开),没有了调用的开销,效率也很高
	2、是一个真正的函数,编译器在调用一个内联函数时,首先会检查参数的类型,然后进行一些列的相关检查
	3、可以作为某个类的成员函数,这样就可以使用类的保护成员及私有成员

24. 内联函数的使用场合

1inline函数可以完全替代宏函数
2、类中的数据成员,一般被定义为私有的或者保护的
  把读写数据成员的成员函数定义为inline函数,将会获得比较好的效率
3、类中
  成员函数的定义被放在类声明中-->自动建议编译器把它编译成inline函数
  定义被放在类声明之外,那么要加上inline关键字,才会建议编译器把它编译为成内联函数

25. 为什么不把所有的函数都定义为内联函数

内联函数是以代码膨胀(复制)为代价的,仅仅省去了函数调用的开销;使程序的总代码量增大,消耗更多的内存空间
如果执行函数体内代码的时间相比于函数调用的开销较大,那么效率的收获会很少

以下情况不建议使用内联函数
	1、函数体代码较长,使用内联函数将导致内存消耗代价较高
	2、函数体内出现循环,执行函数体内代码的时间要比函数调用的开销大
	3、类的构造函数和析构函数容易让人误解成使用内联更有效。
	  要当心构造函数和析构函数可能会隐藏一些行为,比如“偷偷地”执行了基类或成员对象的构造函数和析构函数
	  
所以不要随便地将构造函数和析构函数的定义体放在类声明中!

一个好的编译器将根据函数的定义体,自动取消不值得的内联(这说明了inline不应该出现在函数的声明中)

26. 内联函数和宏定义的区别

1、内联函数在编译时展开,宏在预编译时展开
2、在编译时,内联函数可以直接被镶嵌到目标代码中,而宏只是一个简单的文本替换
3、内联函数可以完成诸如类型检测、语句是否正确等编译功能,宏函数就不具备这样的功能
3、宏函数不是函数,inline函数是函数
4、宏函数在定义时要小心处理宏参数(一般情况是把参数用括号括起来),否则容易出现二义性。而内联函数定义时不会出现二义性
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值