C++面试题学习-1

C++的常考面试题:

以下资料整理转自牛客网
牛客网习题链接

试题1

下面代码会出现什么问题?

1
2
3
4
5
6
7
8
9
10
11
char *GetMemory( void )
{
char p[] = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf( str );
}

错误的地方是p[]未函数内部的局部自动变量,在函数返回后,内存就已经被释放了.错误点:

1
2
char p[] = "hello world"; 
return p;

试题2

问题代码:

1
2
3
4
5
6
7
8
9
10
11
void GetMemory( char **p, int num )
{
*p = (char *) malloc( num );
}
void Test( void )
{
char *str = NULL;
GetMemory( &str, 100 );
strcpy( str, "hello" );
printf( str );
}

代码错误的地方:

  1. 针对malloc应该对应free函数释放
  2. 针对malloc的内存申请,没有添加对应的申请成功的判断
  3. 输出函数printf(str)改为printf(“%s”,str),进行格式化输出,(这不是python的print!!!!)

修正代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void 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
2
3
4
5
6
7
void Test( void )
{
char *str = (char *) malloc( 100 );
strcpy( str, "hello" );
free( str );
... //省略的其它语句
}

同样的问题,内存分配以后指针str未加判断

1
assert(str!=NULL)

释放指针str置位NULL,防止其变为野指针

试题4

问题代码

1
2
3
4
5
6
7
swap( int* p1,int* p2 )
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}

  1. 函数没有类型!!!!(这不是python)
  2. 随便地就申请一个int指针p,其未初始化为NULL,野指针可能指向系统区,很危险

修正代码1:

1
2
3
4
5
6
7
void swap (int *p1,int *p2)
{
int *p =NULL;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}

修正代码2:

1
2
3
4
5
6
7
void 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
6
void Func ( char str[100] )
{
sizeof( str ) = ?
}
void *p = malloc( 100 );
sizeof ( p ) = ?

参考答案:

1
2
3
4
sizeof( str ) = 4   
sizeof ( p ) = 4
【剖析】
Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

数组名的使用注意项:

  1. 数组名指代一种数据结构,这种数据结构就是数组;
  2. 数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;
  3. 数组名作为函数形参时,沦为普通指针。
    Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str ) 、sizeof ( p ) 都为4。
1
2
3
char str[10];
cout << sizeof(str) << endl;
//输出结果为10,str指代数据结构char[10]。

试题7

写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?
least = MIN(*p++, b);

1
#define MIN(A,B) ((A)<=(B)?(A):(B))

错误答案:

1
2
3
#define MIN(A,B) (A)<=(B)?(A):(B)
#define MIN(A,B) (A<=B?A:B)
#define MIN(A,B) ((A)<=(B)?(A):(B));

因为宏定义里面针对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
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */

参考解答:
头文件中的编译宏

1
2
3
#ifndef __INCvxWorksh
#define __INCvxWorksh
#endif

的作用是防止被重复引用。
作为一种面向对象的语言,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
2
3
4
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
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAXSIZE 100

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
2
void *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
3
1、复制的内容不同。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
16
typedef 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
2
WAVEFORMAT 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
17
static关键字至少有下列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
4
int Sum( int n )
{
return ( (long)1 + n) * n / 2;  //或return (1l + n) * n / 2;
}

关于C里面的long类型和int类型转换问题:

1
2
3
4
5
6
7
1. 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
4
1byte等于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
2
0X12345678
最右边的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

关于联合题的用法

C中的联合(union)用法

1、什么是联合?

“联合”是一种特殊的类,也是一种构造类型的数据结构。在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,已达到节省空间的目的(还有一个节省空间的类型:位域)。 这是一个非常特殊的地方,也是联合的特征。另外,同struct一样,联合默认访问权限也是公有的,并且,也具有成员函数。

2、联合与结构的区别?

“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和(空结构除外,同时不考虑边界调整)。而在“联合”中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。应该说明的是, 这里所谓的共享不是指把多个成员同时装入一个联合变量内, 而是指该联合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。
上述博客中的一个理解的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
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
2
AB 
6261

从上例结果可以看出: 当给i赋值后, 其低八位也就是first和second的值; 当给first和second赋字符后, 这两个字符的ASCII码也将作为i 的低八位和高八位。

该题的解法:

1
2
3
4
5
6
7
8
9
10
11
12
int checkCPU()
{
{
union w
{
int a;
char b;
} c;
c.a = 1;
return (c.b == 1);
}
}

热评文章