C/C++
C/C++
关键字
C语言宏中"#“和”##"的用法
- (#)字符串化操作符
作用:将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串。其只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。
如:
#define example( instr ) printf( "the input string is:\t%s\n", #instr ) #define example1( instr ) #instr当使用该宏定义时:
example( abc ); // 在编译时将会展开成:printf("the input string is:\t%s\n","abc") string str = example1( abc ); // 将会展成:string str="abc"
- (##)符号连接操作符
作用:将宏定义的多个形参转换成一个实际参数名。
如:
#define exampleNum( n ) num##n
使用:
int num9 = 9; int num = exampleNum( 9 ); // 将会扩展成 int num = num9
注意:
a. 当用##连接形参时,##前后的空格可有可无。
如:
#define exampleNum( n ) num ## n // 相当于 #define exampleNum( n ) num##n
b. 连接后的实际参数名,必须为实际存在的参数名或是编译器已知的宏定义。
c. 如果##后的参数本身也是一个宏的话,##会阻止这个宏的展开。
#include <stdio.h> #include <string.h> #define STRCPY(a, b) strcpy(a ## _p, #b) int main() { char var1_p[20]; char var2_p[30]; strcpy(var1_p, "aaaa"); strcpy(var2_p, "bbbb"); STRCPY(var1, var2); STRCPY(var2, var1); printf("var1 = %s\n", var1_p); printf("var2 = %s\n", var2_p); //STRCPY(STRCPY(var1,var2),var2); //这里是否会展开为: strcpy(strcpy(var1_p,"var2")_p,"var2“)?答案是否定的: //展开结果将是: strcpy(STRCPY(var1,var2)_p,"var2") //## 阻止了参数的宏展开!如果宏定义里没有用到 # 和 ##, 宏将会完全展开 // 把注释打开的话,会报错:implicit declaration of function 'STRCPY' return 0; }
结果:
var1 = var2 var2 = var1
关键字volatile有什么含意?并举出三个不同的例子?
并行设备的硬件寄存器。存储器映射的硬件寄存器通常加volatile,因为寄存器随时可以被外设硬件修改。当声明指向设备寄存器的指针时一定要用volatile,它会告诉编译器不要对存储在这个地址的数据进行假设。
一个中断服务程序中修改的供其他程序检测的变量。volatile提醒编译器,它后面所定义的变量随时都有可能改变。因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
多线程应用中被几个任务共享的变量。单地说就是防止编译器对代码进行优化.比如如下程序:
XBYTE[2]=0x55; XBYTE[2]=0x56; XBYTE[2]=0x57; XBYTE[2]=0x58;
对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入volatile,编译器会逐一的进行编译并产生相应的机器代码(产生四条代码)。
关键字static的作用是什么?
在函数体,只会被初始化一次,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量(只能被当前文件使用)。
在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用(只能被当前文件使用)。
在C语言中,为什么 static变量只初始化一次?
对于所有的对象(不仅仅是静态对象),初始化都只有一次,而由于静态变量具有“记忆”功能,初始化后,一直都没有被销毁,都会保存在内存区域中,所以不会再次初始化。存放在静态区的变量的生命周期一般比较长,它与整个程序“同生死、共存亡”,所以它只需初始化一次。而auto变量,即自动变量,由于它存放在栈区,一旦函数调用结束,就会立刻被销毁。
extern”C” 的作用是什么?
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
const有什么作用?
- 定义变量(局部变量或全局变量)为常量,例如:
const int N=100;//定义一个常量N N=50; //错误,常量的值不能被修改 const int n; //错误,常量在定义的时候必须初始化
修饰函数的参数,表示在函数体内不能修改这个参数的值。
修饰函数的返回值。
a.如果给用 const修饰返回值的类型为指针,那么函数返回值(即指针)的内容是不能被修改的,而且这个返回值只能赋给被 const修饰的指针。例如:
const char GetString() //定义一个函数 char *str= GetString() //错误,因为str没有被 const修饰 const char *str=GetString() //正确
b.如果用 const修饰普通的返回值,如返回int变量,由于这个返回值是一个临时变量,在函数调用结束后这个临时变量的生命周期也就结束了,因此把这些返回值修饰为 const是没有意义的。
节省空间,避免不必要的内存分配。例如:
#define PI 3.14159//该宏用来定义常量 const doulbe Pi=3.14159//此时并未将P放入只读存储器中 double i=Pi//此时为Pi分配内存,以后不再分配 double I=PI//编译期间进行宏替换,分配内存 double j=Pi//没有内存分配再次进行宏替换,又一次分配内存
什么情况下使用const关键字?
- 修饰一般常量。一般常量是指简单类型的常量。这种常量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。例如:
int const x=2;const int x=2
- 修饰常数组。定义或说明一个常数组可以采用如下格式:
int const a[8]={1,2,3,4,5,6,7,8} const int a[8]={1,2,3,4,5,6,7,8}
- 修饰常对象。常对象是指对象常量,定义格式如下:
class A: const A a: A const a:
定义常对象时,同样要进行初始化,并且该对象不能再被更新。修饰符 const可以放在类名后面,也可以放在类名前面。
- 修饰常指针
const int*p; //常量指针,指向常量的指针。即p指向的内存可以变,p指向的数值内容不可变 int const*p; //同上 int*const p;//指针常量,本质是一个常量,而用指针修饰它。 即p指向的内存不可以变,但是p内存位置的数值可以变 const int* const p;//指向常量的常量指针。即p指向的内存和数值都不可变
修饰常引用。被 const修饰的引用变量为常引用,一旦被初始化,就不能再指向其他对象了。
修饰函数的常参数。 const修饰符也可以修饰函数的传递参数,格式如下:
void Fun(const int Var)
告诉编译器Var在函数体中不能被改变,从而防止了使用者一些无意的或错误的修改。
- 修饰函数的返回值。 const修饰符也可以修饰函数的返回值,表明该返回值不可被改变,格式如下:
const int FunI(); const MyClass Fun2();
- 在另一连接文件中引用 const常量。使用方式有
extern const int 1: extern const int j=10;
new/delete与malloc/free的区别是什么?
new、delete是C++中的操作符,而malloc和free是标准库函数。
对于非内部数据对象来说,只使用malloc是无法完成动态对象要求的,一般在创建对象时需要调用构造函数,对象消亡时,自动的调用析构函数。而malloc free是库函数而不是运算符,不在编译器控制范围之内,不能够自动调用构造函数和析构函数。而NEW在为对象申请分配内存空间时,可以自动调用构造函数,同时也可以完成对对象的初始化。同理,delete也可以自动调用析构函数。而mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。
new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。
strlen("\0") =? sizeof("\0")=?
strlen("\0") =0,sizeof("\0")=2。
strlen用来计算字符串的长度(在C/C++中,字符串是以"\0"作为结束符的),它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描直到碰到第一个字符串结束符\0为止,然后返回计数器值sizeof是C语言的关键字,它以字节的形式给出了其操作数的存储大小,操作数可以是一个表达式或括在括号内的类型名,操作数的存储大小由操作数的类型决定。
sizeof和strlen有什么区别?
strlen与 sizeof的差别表现在以下5个方面。
sizeof是运算符(是不是被弄糊涂了?事实上, sizeof既是关键字,也是运算符,但不是函数),而strlen是函数。 sizeof后如果是类型,则必须加括弧,如果是变量名,则可以不加括弧。
sizeof运算符的结果类型是 size_t,它在头文件中 typedef为 unsigned int类型。该类型保证能够容纳实现所建立的最大对象的字节大小
sizeof可以用类型作为参数, strlen只能用char*作参数,而且必须是以“0结尾的。 sizeof还可以以函数作为参数,如int g(),则 sizeof(g())的值等于 sizeof( int的值,在32位计算机下,该值为4。
大部分编译程序的 sizeof都是在编译的时候计算的,所以可以通过 sizeof(x)来定义数组维数。而 strlen则是在运行期计算的,用来计算字符串的实际长度,不是类型占内存的大小。例如, char str[20] = "0123456789”,字符数组str是编译期大小已经固定的数组,在32位机器下,为 sizeof(char)20=20,而其 strlen大小则是在*运行期**确定的,所以其值为字符串的实际长度10。
当数组作为参数传给函数时,传递的是指针,而不是数组,即传递的是数组的首地址。
不使用 sizeof,如何求int占用的字节数?
#include <stdio.h> #define MySizeof(Value) (char *)(&value+1)-(char*)&value int main() { int i ; double f; double *q; printf("%d\r\n",MySizeof(i)); printf("%d\r\n",MySizeof(f)); printf("%d\r\n",MySizeof(a)); printf("%d\r\n",MySizeof(q)); return 0; }
输出为:
4 8 32 4
上例中,(char*)& Value
返回 Value的地址的第一个字节,(char*)(& Value+1)
返回value的地址的下一个地址的第一个字节,所以它们之差为它所占的字节数。
C语言中 struct与 union的区别是什么?
struct(结构体)与 union(联合体)是C语言中两种不同的数据结构,两者都是常见的复合结构,其区别主要表现在以下两个方面。
结构体与联合体虽然都是由多个不同的数据类型成员组成的,但不同之处在于联合体中所有成员共用一块地址空间,即联合体只存放了一个被选中的成员,而结构体中所有成员占用空间是累加的,其所有成员都存在,不同成员会存放在不同的地址。在计算一个结构型变量的总长度时,其内存空间大小等于所有成员长度之和(需要考虑字节对齐),而在联合体中,所有成员不能同时占用内存空间,它们不能同时存在,所以一个联合型变量的长度等于其最长的成员的长度。
对于联合体的不同成员赋值,将会对它的其他成员重写,原来成员的值就不存在了,而对结构体的不同成员赋值是互不影响的。
举个例子。下列代码执行结果是多少?
typedef union {double i; int k[5]; char c;}DATE; typedef struct data( int cat; DATE cow;double dog;)too; DATE max; printf ("%d", sizeof(too)+sizeof(max));
假设为32位机器,int型占4个字节, double型占8个字节,char型占1个字节,而DATE是一个联合型变量,联合型变量共用空间,uion里面最大的变量类型是int[5],所以占用20个字节,它的大小是20,而由于 union中 double占了8个字节,因此 union是要8个字节对齐,所占内存空间为8的倍数。为了实现8个字节对齐,所占空间为24.而data是一个结构体变量,每个变量分开占用空间,依次为 sizeof(int)+ sizeof(DATE)+ sizeof( double)=4+24+8=36按照8字节对齐,占用空间为40,所以结果为40+24=64。
左值和右值是什么?
左值是指可以出现在等号左边的变量或表达式,它最重要的特点就是可写(可寻址)。也就是说,它的值可以被修改,如果一个变量或表达式的值不能被修改,那么它就不能作为左值。
右值是指只可以出现在等号右边的变量或表达式。它最重要的特点是可读。一般的使用场景都是把一个右值赋值给一个左值。
通常,左值可以作为右值,但是右值不一定是左值。
什么是短路求值?
#include <stdio.h> int main() { int i = 6; int j = 1; if(i>0||(j++)>0); printf("%D\r\n",j); return 0; }
输出结果为1。
输出为什么不是2,而是1呢?其实,这里就涉及一个短路计算的问题。由于i语句是个条件判断语句,里面是有两个简单语句进行或运算组合的复合语句,因为或运算中,只要参与或运算的两个表达式的值都为真,则整个运算结果为真,而由于变量i的值为6,已经大于0了,而该语句已经为true,则不需要执行后续的j+操作来判断真假,所以后续的j++操作不需要执行,j的值仍然为1。
因为短路计算的问题,对于&&操作,由于在两个表达式的返回值中,如果有一个为假则整个表达式的值都为假,如果前一个语句的返回值为 false,则无论后一个语句的返回值是真是假,整个条件判断都为假,不用执行后一个语句,而a>b的返回值为 false,程序不执行表达式n=c>d,所以,n的值保持为初值2。
++a和a++有什么区别?两者是如何实现的?
a++的具体运算过程为
int temp = a; a=a+1; return temp;
++a的具体运算过程为
a=a+1; return a;
后置自增运算符需要把原来变量的值复制到一个临时的存储空间,等运算结束后才会返回这个临时变量的值。所以前置自增运算符效率比后置自增要高
联系作者
关于作者
作者在准备秋招的过程中,凭借这份资料,最后顺利拿到了oppo,小米,兆易创新,全志科技,海康威视等十余家家公司的offer。现将这部分资料分享出来,希望能对大家有帮助!
关于嵌入式软件工程师笔试面试指南
嵌入式软件工程师笔试面试指南,详细分成了简历书写,面试技巧,面经总结,笔试面试八股文总结等四个部分。
其中,八股文又分成了C/C++,数据结构与算法分析,Arm体系与架构,Linux驱动开发,操作系统,网络编程,名企笔试真题等七个部分。
无论是做嵌入式应用层还是嵌入式底层,一定会对你有帮助的(没有帮助来找我领红包)。这些内容均会同步更新到github仓库中(内含PDF版本的获取方式)。
因个人能力有限,可能会有一些错误。大家发现错误,可以在github提交issues,勘误我也会整理出来,第一时间通知大家。
这些内容都是我熬夜整理的,创作不易,大家不要忘了点击「赞」支持下,也算没有白白熬夜,对得起我掉的一根根头发。
github:https://github.com/ZhongYi-LinuxDriverDev/EmbeddedSoftwareEngineerInterview