C++的常考面试题:
以下资料整理转自牛客网
牛客网习题链接
试题1
下面代码会出现什么问题?1
2
3
4
5
6
7
8
9
10
11char *GetMemory( void )
{
char p[] = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf( str );
}
错误的地方是p[]未函数内部的局部自动变量,在函数返回后,内存就已经被释放了.错误点:1
2char p[] = "hello world";
return p;
试题2
问题代码:1
2
3
4
5
6
7
8
9
10
11void GetMemory( char **p, int num )
{
*p = (char *) malloc( num );
}
void Test( void )
{
char *str = NULL;
GetMemory( &str, 100 );
strcpy( str, "hello" );
printf( str );
}
代码错误的地方:
- 针对malloc应该对应free函数释放
- 针对malloc的内存申请,没有添加对应的申请成功的判断
- 输出函数printf(str)改为printf(“%s”,str),进行格式化输出,(这不是python的print!!!!)
修正代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void GetMemory( char **p, int num )
{
*p = (char *) malloc( num );
if(*p==NULL){
fprintf(stderr,"malloc for *p is failed\n")
}
}
void Test( void )
{
char *str = NULL;
GetMemory( &str, 100 );
strcpy( str, "hello" );
//printf( str );
printf("%s",str)
free(str);
str=NULL
}
试题3
1 | void Test( void ) |
同样的问题,内存分配以后指针str未加判断1
assert(str!=NULL)
释放指针str置位NULL,防止其变为野指针
试题4
问题代码1
2
3
4
5
6
7swap( int* p1,int* p2 )
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
- 函数没有类型!!!!(这不是python)
- 随便地就申请一个int指针p,其未初始化为NULL,野指针可能指向系统区,很危险
修正代码1:1
2
3
4
5
6
7void swap (int *p1,int *p2)
{
int *p =NULL;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
修正代码2:1
2
3
4
5
6
7void swap (int *p1,int *p2)
{
int p ;
p = *p1;
*p1 = *p2;
*p2 = p;
}
试题5
分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)1
2
3
4
5
6
7
8
9// BOOL型变量
if(!var)
//int 型变量
if(var==0)
//float型变量
const float EPSINON=0.00001;
if((var>=-EPSINON)&&(var<=EPSINON))
//指针型变量
if(var==NULL)
试题6
以下为Windows NT下的32位C++程序,请计算sizeof的值1
2
3
4
5
6void Func ( char str[100] )
{
sizeof( str ) = ?
}
void *p = malloc( 100 );
sizeof ( p ) = ?
参考答案:1
2
3
4sizeof( str ) = 4
sizeof ( p ) = 4
【剖析】
Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。
数组名的使用注意项:
- 数组名指代一种数据结构,这种数据结构就是数组;
- 数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;
- 数组名作为函数形参时,沦为普通指针。
Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str ) 、sizeof ( p ) 都为4。
1 | char str[10]; |
试题7
写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?
least = MIN(*p++, b);1
错误答案:1
2
3
因为宏定义里面针对A和B不能单纯地看待为变量,而是应该将其看作为表达式去看待,谨慎地将宏定义中的“参数”和整个宏用用括弧括起,最后一个小儿科的问题!!!宏定义不加分号!!!
第二个小问题
宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))
对MIN(*p++, b)
的作用结果是:1
((*p++) <= (b) ? (*p++) : (b))
这个表达式会产生副作用,指针p会作2次++自增操作。
试题8
为什么标准头文件都有类似以下的结构?1
2
3
4
5
6
7
8
9
10
extern "C" {
/*...*/
}
参考解答:
头文件中的编译宏1
2
3
的作用是防止被重复引用。
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo(int x, int y);
该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int
之类的名字._foo_int_int
这样的名字包含了函数名和函数参数数量及类型信息,C++就是考这种机制来实现函数重载的。
为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"
来解决名字匹配问题,函数声明前加上extern "C"
后,则编译器就会按照C语言的方式将该函数编译为_foo
,这样C语言中就可以调用C++的函数了。
试题9
编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是“abcdefghi”如果n=2,移位后应该是“hiabcdefg” 函数头是这样的:1
2//pStr是指向以'\0'结尾的字符串的指针
//steps是要求移动的n
1 | void LoopMove ( char * pStr, int steps ) |
给出的参考答案:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void LoopMove1(char *str,int steps)
{
int len=strlen(str);
char tmp[MAXSIZE];
strcpy(tmp,str+len-steps);
strcpy(tmp+steps,str);
*(tmp+len)='/0';
strcpy(str,tmp);
}
void LoopMove2(char *str, int steps)
{
int len = strlen(str);
char tmp[MAXSIZE];
memcpy(tmp, str+len-steps, steps);
memcpy(str+steps, str, len-steps);
memcpy(str, tmp, steps);
}
//test
int main()
{
char s[11];
//memset(s,0xff,sizeof(s[0])*11);
memset(s,0xff,sizeof(s))
strcpy(s,"abcdefghi");
printf("before move is: %s \n",s);
printf("move 3 steps\n");
LoopMove2(s,3);
printf("after move is: %s\n",s);
return 1;
}
但是代码在Linux用gcc编译并不理想,会出现栈溢出(stack smashing detect),解决的原因是memset函数的使用过程中对sizeof的理解错了,sizeof(s)就是字符串s的长度11,memset(s,0xff,sizeof(s)*11)
是错误的!!!1
2void *memset(void *s,int c,size_t n)
//总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。函数包含在string的头文件中
但是LoopMove1*(tmp+len)='/0';
是有warning1的不建议操作
给出标准函数memcpy(定义在string中)函数原型1
2函数原型:void *memcpy(void str,const void *s,size_t n);
//功能 c和c++使用的内存拷贝函数.从源s所指的内存地址的起始位置开始拷贝n个字节到目标str所指的内存地址的起始位置中
strcpy和memcpy主要有以下3方面的区别。1
2
31、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
试题10
已知WAV文件格式如下表,打开一个WAV文件,以适当的数据结构组织WAV文件头并解析WAV格式的各项信息。
WAVE文件格式说明表
答案解析:
将WAV文件格式定义为结构体WAVEFORMAT:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16typedef struct tagWaveFormat
{
char cRiffFlag[4];
UIN32 nFileLen;
char cWaveFlag[4];
char cFmtFlag[4];
char cTransition[4];
UIN16 nFormatTag ;
UIN16 nChannels;
UIN16 nSamplesPerSec;
UIN32 nAvgBytesperSec;
UIN16 nBlockAlign;
UIN16 nBitNumPerSample;
char cDataFlag[4];
UIN32 nAudioLength;
} WAVEFORMAT;
假设WAV文件内容读出后存放在指针buffer开始的内存单元内,则分析文件格式的代码很简单,为:1
2WAVEFORMAT waveFormat;
memcpy( &waveFormat, buffer,sizeof( WAVEFORMAT ) );
直接通过访问waveFormat的成员,就可以获得特定WAV文件的各项格式信息。
拓展:关于C中的一些变量类型
c语言基本数据类型short、int、long、char、float、double
习题11
请说出static和const关键字尽可能多的作用
牛客网中给出的答案1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17static关键字至少有下列n个作用:
(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
const关键字至少有下列n个作用:
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的 成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。例如:
const classA operator*(const classA& a1,const classA& a2);
operator*的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错:
classA a, b, c;
(a * b) = c; // 对a*b的结果赋值
操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。
习题12
写一个函数返回1+2+3+…+n的值(假定结果不会超过长整型变量的范围)
参考答案(简单清晰是王道):1
2
3
4int Sum( int n )
{
return ( (long)1 + n) * n / 2; //或return (1l + n) * n / 2;
}
关于C里面的long类型和int类型转换问题:1
2
3
4
5
6
71. long 是C语言的一个关键字,代表一种数据类型,中文为长整型。
2. long是long int的简写,也就是说,在C语言中long int类型和long类型是相同的。
3. 每个long型占4个字节,在32位编译系统下,long和int占的空间是相同的。这也导致了long型变量使用的越来越少了。
4. long型可以表示的整型数字范围为-2,147,483,648 ~ 2,147,483,647, 即-2^32 ~ 2^32-1。
在用在C的格式化输入输出时,long型的格式化字符为"%ld"。
5. long同其它整型类型一样,可以同unsigned 联合使用,形成unsigned long,即无符号长整型, 其格式化字符为"%lu"。
6. 在部分编译器下,比如gcc, 两个long合用,即long long类型,表示C语言目前最长的系统整型类型,每个long long类型占8字节,64位。其格式化字符为"%lld"。
习题13
请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1
这里有个关于int和char的大小,以及byte和位一直困扰我很久,就这个题目,重新捋直,1
2
3
41byte等于8bit
32位计算机中内存表示形式0xFFFFFFFF,总共有8个8进制位的表示,一个八进制的位数需要4bit!!
char是1byte(字节),也就是只有2个8进制数表示,内存表现形式未0x00-0xFF(0-255)的大小,儿一般的ASCII编码对应的就是char
int是2byte(字节),等于4个8进制数表示,0x0000-0xFFFF(如果无符号,则0-2^16)大小
在捋一捋关于字和字节的理解:1
2
3
4
5
6
7
8
9不同的字符所占的字节是不同的。
ASCII码:
一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数,换算为十进制。最小值0,最大值255。如一个ASCII码就是一个字节。
UTF-8编码:
一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。
Unicode编码:
一个英文等于两个字节,一个中文(含繁体)等于两个字节。
符号:
英文标点占一个字节,中文标点占两个字节。举例:英文句号“.”占1个字节的大小,中文句号“。”占2个字节的大小。
关于大端存放和小端存放的问题:
小段CPU对操作数的存放是从低字节到高字节
大端CPU对操作数的存放是从高字节到低字节
关于什么是高低字节?1
20X12345678
最右边的8是最低位也就是低字节!!!!
16bit宽的数0x1234
在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址 | 存放内容 |
---|---|
0x4000 | 0x34 |
0x4001 | 0x12 |
而在Big-endian模式CPU内存中的存放方式则为:
内存地址 | 存放内容 |
---|---|
0x4000 | 0x12 |
0x4001 | 0x34 |
32bit宽的数0x12345678
在Little-endian模式CPU内存中的存放方式(假设从地址0x4000
开始存放)为:
内存地址 | 存放内容 |
---|---|
0x4000 | 0x78 |
0x4001 | 0x56 |
0x4002 | 0x34 |
0x4003 | 0x12 |
而在Big-endian模式CPU内存中的存放方式则为:
内存地址 | 存放内容 |
---|---|
0x4000 | 0x12 |
0x4001 | 0x34 |
0x4002 | 0x56 |
0x4003 | 0x78 |
关于联合题的用法
1、什么是联合?
“联合”是一种特殊的类,也是一种构造类型的数据结构。在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,已达到节省空间的目的(还有一个节省空间的类型:位域)。 这是一个非常特殊的地方,也是联合的特征。另外,同struct一样,联合默认访问权限也是公有的,并且,也具有成员函数。
2、联合与结构的区别?
“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和(空结构除外,同时不考虑边界调整)。而在“联合”中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。应该说明的是, 这里所谓的共享不是指把多个成员同时装入一个联合变量内, 而是指该联合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。
上述博客中的一个理解的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void main()
{
union number
{ /*定义一个联合*/
int i;
struct
{ /*在联合中定义一个结构*/
char first;
char second;
}half;
}num;
num.i=0x4241; /*联合成员赋值*/
printf("%c%c\n", num.half.first, num.half.second);
num.half.first='a'; /*联合中结构成员赋值*/
num.half.second='b';
printf("%x\n", num.i);
getchar();
}
输出结果为:
1 | AB |
从上例结果可以看出: 当给i赋值后, 其低八位也就是first和second的值; 当给first和second赋字符后, 这两个字符的ASCII码也将作为i 的低八位和高八位。
该题的解法:
1 | int checkCPU() |