C++之基础语法

如果你想在校招中顺利拿到更好的offer,阿秀建议你多看看前人的经验 ,比如准备简历实习上岸经历校招总结阿里、字节、腾讯、美团等一二线大厂真实面经也欢迎来一起参加秋招打卡活动 等;如果你是计算机小白,学习/转行/校招路上感到迷茫或者需要帮助,可以点此联系阿秀;免费分享阿秀个人学习计算机以来的收集到的好资源,点此白嫖;如果你需要《阿秀的学习笔记》网站中求职相关知识点的PDF版本的话,可以点此下载

# 101、程序在执行int main(int argc, char *argv[])时的内存结构,你了解吗?

参数的含义是程序在命令行下运行的时候,需要输入argc 个参数,每个参数是以char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针

char * 指向的内存中,数组的中元素的个数为 argc 个,第一个参数为程序的名称。

# 102、volatile关键字的作用?

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

volatile用在如下的几个地方:

  1. 中断服务程序中修改的供其它程序检测的变量需要加volatile;
  2. 多任务环境下各任务间共享的标志应该加volatile;
  3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

# 103、如果有一个空类,它会默认添加哪些函数?

1)  Empty(); // 缺省构造函数//
2)  Empty( const Empty& ); // 拷贝构造函数//
3)  ~Empty(); // 析构函数//
4)  Empty& operator=( const Empty& ); // 赋值运算符//
1
2
3
4

# 104、C++中标准库是什么?

  1. C++ 标准库可以分为两部分:

标准函数库: 这个库是由通用的、独立的、不属于任何类的函数组成的。函数库继承自 C 语言。

面向对象类库: 这个库是类及其相关函数的集合。

  1. 输入/输出 I/O、字符串和字符处理、数学、时间、日期和本地化、动态分配、其他、宽字符函数

  2. 标准的 C++ I/O 类、String 类、数值类、STL 容器类、STL 算法、STL 函数对象、STL 迭代器、STL 分配器、本地化库、异常处理类、杂项支持库

# 105、你知道const char* 与string之间的关系是什么吗?

  1. string 是c++标准库里面其中一个,封装了对字符串的操作,实际操作过程我们可以用const char*给string类初始化

  2. 三者的转化关系如下所示:

a)  string转const char* 

string s = “abc”; 

const char* c_s = s.c_str(); 

b)  const char* 转string,直接赋值即可 

const char* c_s = “abc”; 
 string s(c_s); 

c)  string 转char* 
 string s = “abc”; 
 char* c; 
 const int len = s.length(); 
 c = new char[len+1]; 
 strcpy(c,s.c_str()); 

d)  char* 转string 
 char* c = “abc”; 
 string s(c); 

e)  const char*char* 
 const char* cpc = “abc”; 
 char* pc = new char[strlen(cpc)+1]; 
 strcpy(pc,cpc);

f)  char*const char*,直接赋值即可 
 char* pc = “abc”; 
 const char* cpc = pc;
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

# 106、你什么情况用指针当参数,什么时候用引用,为什么?

  1. 使用引用参数的主要原因有两个:

程序员能修改调用函数中的数据对象

通过传递引用而不是整个数据–对象,可以提高程序的运行速度

  1. 一般的原则: 对于使用引用的值而不做修改的函数:

如果数据对象很小,如内置数据类型或者小型结构,则按照值传递;

如果数据对象是数组,则使用指针(唯一的选择),并且指针声明为指向const的指针;

如果数据对象是较大的结构,则使用const指针或者引用,已提高程序的效率。这样可以节省结构所需的时间和空间;

如果数据对象是类对象,则使用const引用(传递类对象参数的标准方式是按照引用传递);

  1. 对于修改函数中数据的函数:

如果数据是内置数据类型,则使用指针

如果数据对象是结构,则使用引用或者指针

如果数据是类对象,则使用引用

也有一种说法认为:“如果数据对象是数组,则只能使用指针”,这是不对的,比如

template<typename T, int N>
void func(T (&a)[N])
{
    a[0] = 2;
}

int main()
{
    int a[] = { 1, 2, 3 };
    func(a);
    cout << a[0] << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 107、你知道静态绑定和动态绑定吗?讲讲?

  1. 对象的静态类型:对象在声明时采用的类型。是在编译期确定的。

  2. 对象的动态类型:目前所指对象的类型。是在运行期决定的。对象的动态类型可以更改,但是静态类型无法更改。

  3. 静态绑定:绑定的是对象的静态类型,某特性(比如函数依赖于对象的静态类型,发生在编译期。)

  4. 动态绑定:绑定的是对象的动态类型,某特性(比如函数依赖于对象的动态类型,发生在运行期。)

# 108、如何设计一个计算仅单个子类的对象个数?

1、为类设计一个static静态变量count作为计数器;

2、类定义结束后初始化count;

3、在构造函数中对count进行+1;

4、 设计拷贝构造函数,在进行拷贝构造函数中进行count +1,操作;

5、设计复制构造函数,在进行复制函数中对count+1操作;

6、在析构函数中对count进行-1;

感谢微信好友“铁锤哥哥”勘误:“计算子类个数”->计算仅单个子类的对象个数-2021.06.28

# 109、怎么快速定位错误出现的地方?

1、如果是简单的错误,可以直接双击错误列表里的错误项或者生成输出的错误信息中带行号的地方就可以让编辑窗口定位到错误的位置上。

2、对于复杂的模板错误,最好使用生成输出窗口。

多数情况下出发错误的位置是最靠后的引用位置。如果这样确定不了错误,就需要先把自己写的代码里的引用位置找出来,然后逐个分析了。

# 110、成员初始化列表会在什么时候用到?它的调用过程是什么?

  1. 当初始化一个引用成员变量时;

  2. 初始化一个const成员变量时;

  3. 当调用一个基类的构造函数,而构造函数拥有一组参数时;

  4. 当调用一个成员类的构造函数,而他拥有一组参数;

  5. 编译器会一一操作初始化列表,以适当顺序在构造函数之内安插初始化操作,并且在任何显示用户代码前。list中的项目顺序是由类中的成员声明顺序决定的,不是初始化列表中的排列顺序决定的。

# 111、在进行函数参数以及返回值传递时,可以使用引用或者值传递,其中使用引用的好处有哪些?

对比值传递,引用传参的好处:

1)在函数内部可以对此参数进行修改

2)提高函数调用和运行的效率(因为没有了传值和生成副本的时间和空间消耗)

如果函数的参数实质就是形参,不过这个形参的作用域只是在函数体内部,也就是说实参和形参是两个不同的东西,要想形参代替实参,肯定有一个值的传递。函数调用时,值的传递机制是通过“形参=实参”来对形参赋值达到传值目的,产生了一个实参的副本。即使函数内部有对参数的修改,也只是针对形参,也就是那个副本,实参不会有任何更改。函数一旦结束,形参生命也宣告终结,做出的修改一样没对任何变量产生影响。

用引用作为返回值最大的好处就是在内存中不产生被返回值的副本。

但是有以下的限制:

1)不能返回局部变量的引用。因为函数返回以后局部变量就会被销毁

2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak

3)可以返回类成员的引用,但是最好是const。因为如果其他对象可以获得该属性的非常量的引用,那么对该属性的单纯赋值就会破坏业务规则的完整性。

# 112、说一说strcpy、sprintf与memcpy这三个函数的不同之处

  1. 操作对象不同

① strcpy的两个操作对象均为字符串

② sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串

③ memcpy的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。

  1. 执行效率不同

memcpy最高,strcpy次之,sprintf的效率最低。

  1. 实现功能不同

① strcpy主要实现字符串变量间的拷贝

② sprintf主要实现其他数据类型格式到字符串的转化

③ memcpy主要是内存块间的拷贝。

# 113、将引用作为函数参数有哪些好处?

  1. 传递引用给函数与传递指针的效果是一样的。

这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

  1. 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;

而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;

如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

  1. 使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;

另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

# 114、你知道数组和指针的区别吗?

  1. 数组在内存中是连续存放的,开辟一块连续的内存空间;数组所占存储空间:sizeof(数组名);数组大小:sizeof(数组名)/sizeof(数组元素数据类型);

  2. 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。

  3. 编译器为了简化对数组的支持,实际上是利用指针实现了对数组的支持。具体来说,就是将表达式中的数组元素引用转换为指针加偏移量的引用。

  4. 在向函数传递参数的时候,如果实参是一个数组,那用于接受的形参为对应的指针。也就是传递过去是数组的首地址而不是整个数组,能够提高效率;

  5. 在使用下标的时候,两者的用法相同,都是原地址加上下标值,不过数组的原地址就是数组首元素的地址是固定的,指针的原地址就不是固定的。

# 115、如何阻止一个类被实例化?有哪些方法?

  1. 将类定义为抽象基类或者将构造函数声明为private;

  2. 不允许类外部创建类对象,只能在类内部创建对象

# 116、 如何禁止程序自动生成拷贝构造函数?

  1. 为了阻止编译器默认生成拷贝构造函数和拷贝赋值函数,我们需要手动去重写这两个函数,某些情况下,为了避免调用拷贝构造函数和拷贝赋值函数,我们需要将他们设置成private,防止被调用。

  2. 类的成员函数和friend函数还是可以调用private函数,如果这个private函数只声明不定义,则会产生一个连接错误;

  3. 针对上述两种情况,我们可以定一个base类,在base类中将拷贝构造函数和拷贝赋值函数设置成private,那么派生类中编译器将不会自动生成这两个函数,且由于base类中该函数是私有的,因此,派生类将阻止编译器执行相关的操作。

# 117、你知道Debug和Release的区别是什么吗?

  1. 调试版本,包含调试信息,所以容量比Release大很多,并且不进行任何优化(优化会使调试复杂化,因为源代码和生成的指令间关系会更复杂),便于程序员调试。Debug模式下生成两个文件,除了.exe或.dll文件外,还有一个.pdb文件,该文件记录了代码中断点等调试信息;

  2. 发布版本,不对源代码进行调试,编译时对应用程序的速度进行优化,使得程序在代码大小和运行速度上都是最优的。(调试信息可在单独的PDB文件中生成)。Release模式下生成一个文件.exe或.dll文件。

  3. 实际上,Debug 和 Release 并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。

# 118、main函数的返回值有什么值得考究之处吗?

程序运行过程入口点main函数,main()函数返回值类型必须是int,这样返回值才能传递给程序激活者(如操作系统)表示程序正常退出。

main(int args, char **argv) 参数的传递。参数的处理,一般会调用getopt()函数处理,但实践中,这仅仅是一部分,不会经常用到的技能点。

# 119、模板会写吗?写一个比较大小的模板函数

#include<iostream> 

using namespace std; 
template<typename type1,typename type2>//函数模板 

type1 Max(type1 a,type2 b) 
{ 
   return a > b ? a : b; 
} 

void main() 
 { 
  cout<<"Max = "<<Max(5.5,'a')<<endl; 
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

其实该模板有个比较隐晦的bug,那就是a、b只有在能进行转型的时候才能进行比较,否则 a > b 这一步是会报错的。

这个时候往往需要对于 > 号进行重载,这代码量瞬间上来了。

感谢微信好友“江河”指出a、b转型bug,已采纳-2021.06.28

# 120、strcpy函数和strncpy函数的区别?哪个函数更安全?

  1. 函数原型
char* strcpy(char* strDest, const char* strSrc)
char *strncpy(char *dest, const char *src, size_t n)
1
2
    • strcpy函数: 如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出(buffer Overflow)的错误情况,在编写程序时请特别留意,或者用strncpy()来取代。

    • strncpy函数:用来复制源字符串的前n个字符,src 和 dest 所指的内存区域不能重叠,且 dest 必须有足够的空间放置n个字符。

    • 如果目标长>指定长>源长,则将源长全部拷贝到目标长,自动加上’\0’
    • 如果指定长<源长,则将源长中按指定长度拷贝到目标字符串,不包括’\0’
    • 如果指定长>目标长,运行时错误 ;