C/C++

C/C++

关键字

C语言宏中"#“和”##"的用法

  1. (#)字符串化操作符

作用:将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串。其只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。

如:

#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"
  1. (##)符号连接操作符

作用:将宏定义的多个形参转换成一个实际参数名。

如:

#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有什么含意?并举出三个不同的例子?

  1. 并行设备的硬件寄存器。存储器映射的硬件寄存器通常加volatile,因为寄存器随时可以被外设硬件修改。当声明指向设备寄存器的指针时一定要用volatile,它会告诉编译器不要对存储在这个地址的数据进行假设。

  2. 一个中断服务程序中修改的供其他程序检测的变量。volatile提醒编译器,它后面所定义的变量随时都有可能改变。因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

  3. 多线程应用中被几个任务共享的变量。单地说就是防止编译器对代码进行优化.比如如下程序:

XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;

对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2]=0x58即忽略前三条语句,只产生一条机器代码)。如果键入volatile,编译器会逐一的进行编译并产生相应的机器代码(产生四条代码)。

关键字static的作用是什么?

  1. 在函数体,只会被初始化一次,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

  2. 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量(只能被当前文件使用)。

  3. 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用(只能被当前文件使用)。

在C语言中,为什么 static变量只初始化一次?

对于所有的对象(不仅仅是静态对象),初始化都只有一次,而由于静态变量具有“记忆”功能,初始化后,一直都没有被销毁,都会保存在内存区域中,所以不会再次初始化。存放在静态区的变量的生命周期一般比较长,它与整个程序“同生死、共存亡”,所以它只需初始化一次。而auto变量,即自动变量,由于它存放在栈区,一旦函数调用结束,就会立刻被销毁

extern”C” 的作用是什么?

extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

const有什么作用?

  1. 定义变量(局部变量或全局变量)为常量,例如:
const int N=100;//定义一个常量N
N=50; //错误,常量的值不能被修改
const int n; //错误,常量在定义的时候必须初始化
  1. 修饰函数的参数,表示在函数体内不能修改这个参数的值。

  2. 修饰函数的返回值。

    a.如果给用 const修饰返回值的类型为指针,那么函数返回值(即指针)的内容是不能被修改的,而且这个返回值只能赋给被 const修饰的指针。例如:

    const char GetString()  //定义一个函数
    char *str= GetString() //错误,因为str没有被 const修饰
    const char *str=GetString() //正确

    b.如果用 const修饰普通的返回值,如返回int变量,由于这个返回值是一个临时变量,在函数调用结束后这个临时变量的生命周期也就结束了,因此把这些返回值修饰为 const是没有意义的。

  3. 节省空间,避免不必要的内存分配。例如:

    #define PI 3.14159//该宏用来定义常量
    const doulbe Pi=3.14159//此时并未将P放入只读存储器中
    double i=Pi//此时为Pi分配内存,以后不再分配
    double I=PI//编译期间进行宏替换,分配内存
    double j=Pi//没有内存分配再次进行宏替换,又一次分配内存

什么情况下使用const关键字?

  1. 修饰一般常量。一般常量是指简单类型的常量。这种常量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。例如:
int const x=2;const int x=2
  1. 修饰常数组。定义或说明一个常数组可以采用如下格式:
int const a[8]={1,2,3,4,5,6,7,8}
const int a[8]={1,2,3,4,5,6,7,8}
  1. 修饰常对象。常对象是指对象常量,定义格式如下:
class A:
const A a:
A const a:

定义常对象时,同样要进行初始化,并且该对象不能再被更新。修饰符 const可以放在类名后面,也可以放在类名前面。

  1. 修饰常指针
const int*p; //常量指针,指向常量的指针。即p指向的内存可以变,p指向的数值内容不可变
int const*p; //同上
int*const p;//指针常量,本质是一个常量,而用指针修饰它。 即p指向的内存不可以变,但是p内存位置的数值可以变
const int* const p;//指向常量的常量指针。即p指向的内存和数值都不可变
  1. 修饰常引用。被 const修饰的引用变量为常引用,一旦被初始化,就不能再指向其他对象了。

  2. 修饰函数的常参数。 const修饰符也可以修饰函数的传递参数,格式如下:

void Fun(const int Var)

  告诉编译器Var在函数体中不能被改变,从而防止了使用者一些无意的或错误的修改。

  1. 修饰函数的返回值。 const修饰符也可以修饰函数的返回值,表明该返回值不可被改变,格式如下:
const int FunI(); 
const MyClass Fun2();
  1. 在另一连接文件中引用 const常量。使用方式有
extern const int 1:
extern const int j=10;

new/delete与malloc/free的区别是什么?

  1. new、delete是C++中的操作符,而malloc和free是标准库函数。

  2. 对于非内部数据对象来说,只使用malloc是无法完成动态对象要求的,一般在创建对象时需要调用构造函数,对象消亡时,自动的调用析构函数。而malloc free是库函数而不是运算符,不在编译器控制范围之内,不能够自动调用构造函数和析构函数。而NEW在为对象申请分配内存空间时,可以自动调用构造函数,同时也可以完成对对象的初始化。同理,delete也可以自动调用析构函数。而mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。

  3. new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。

strlen("\0") =? sizeof("\0")=?

strlen("\0") =0,sizeof("\0")=2。

strlen用来计算字符串的长度(在C/C++中,字符串是以"\0"作为结束符的),它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描直到碰到第一个字符串结束符\0为止,然后返回计数器值sizeof是C语言的关键字,它以字节的形式给出了其操作数的存储大小,操作数可以是一个表达式或括在括号内的类型名,操作数的存储大小由操作数的类型决定。

sizeof和strlen有什么区别?

strlen与 sizeof的差别表现在以下5个方面。

  1. sizeof是运算符(是不是被弄糊涂了?事实上, sizeof既是关键字,也是运算符,但不是函数),而strlen是函数。 sizeof后如果是类型,则必须加括弧,如果是变量名,则可以不加括弧。

  2. sizeof运算符的结果类型是 size_t,它在头文件中 typedef为 unsigned int类型。该类型保证能够容纳实现所建立的最大对象的字节大小

  3. sizeof可以用类型作为参数, strlen只能用char*作参数,而且必须是以“0结尾的。 sizeof还可以以函数作为参数,如int g(),则 sizeof(g())的值等于 sizeof( int的值,在32位计算机下,该值为4。

  4. 大部分编译程序的 sizeof都是在编译的时候计算的,所以可以通过 sizeof(x)来定义数组维数。而 strlen则是在运行期计算的,用来计算字符串的实际长度,不是类型占内存的大小。例如, char str[20] = "0123456789”,字符数组str是编译期大小已经固定的数组,在32位机器下,为 sizeof(char)20=20,而其 strlen大小则是在*运行期**确定的,所以其值为字符串的实际长度10。

  5. 当数组作为参数传给函数时,传递的是指针,而不是数组,即传递的是数组的首地址。

不使用 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语言中两种不同的数据结构,两者都是常见的复合结构,其区别主要表现在以下两个方面。

  1. 结构体与联合体虽然都是由多个不同的数据类型成员组成的,但不同之处在于联合体中所有成员共用一块地址空间,即联合体只存放了一个被选中的成员,而结构体中所有成员占用空间是累加的,其所有成员都存在,不同成员会存放在不同的地址。在计算一个结构型变量的总长度时,其内存空间大小等于所有成员长度之和(需要考虑字节对齐),而在联合体中,所有成员不能同时占用内存空间,它们不能同时存在,所以一个联合型变量的长度等于其最长的成员的长度

  2. 对于联合体的不同成员赋值,将会对它的其他成员重写,原来成员的值就不存在了,而对结构体的不同成员赋值是互不影响的。

举个例子。下列代码执行结果是多少?

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