c++面试题2023版

c++面试题

一:编程基础

<1.语法基础
1.关键字类
1.new、delete、malloc、free关系**

delete会调用对象的析构函数,和new相应free只会释放内存,new调用构造函数。

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的规定。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不可以把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完毕动态内存分派和初始化工作的运算符new,以及一个能完毕清理与释放内存工作的运算符delete。注意new/delete不是库函数。

new和malloc的区别

(1) new是关键字,需要编译器支持;malloc是库函数,需要头文件支持 new和delete配合使用,new数组时候,释放时需要使用delete[] malloc和free配合使用 (2) new是类型安全的,在申请内存时,不需要进行类型转换 malloc需要强制类型转换 (3) new一个类对象或者结构体对象时,会自动的调用构造函数,delete会自动调用析构函数 malloc不会自动调用,free不会 (4) new申请内存失败后,会抛出bad_alloc异常 malloc申请内存失败后,返回的NULL

2.delete**与** delete []**区别**

delete只会调用一次析构函数,而delete[]会调用每一个成员的析构函数。在More Effective C++中有更为具体的解释:“当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operatordelete来释放内存。”delete与New配套,delete []与new []配套

MemTest* mTest1=newMemTest[10];

MemTest* mTest2=newMemTest;

int*pInt1=newint[10];

int*pInt2=newint;

delete[]pInt1; //-1-

delete[]pInt2; //-2-

delete[]mTest1;//-3-

delete[]mTest2;//-4-

在-4-处报错。

这就说明:对于内建简朴数据类型,delete和delete[]功能是相同的。对于自定义的复杂数据类型,delete和delete[]不能互用。delete[]删除一个数组,delete删除一个指针简朴来说,用new分派的内存用delete删除用new[]分派的内存用delete[]删除delete[]会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。假如你在用delete时没用括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。

3.Ado**与Ado.net的相同与不同?**

除了“可以让应用程序解决存储于DBMS 中的数据“这一基本相似点外,两者没有太多共同之处。但是Ado使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,这也就是说ADO.NET 和ADO是两种数据访问方式。ADO.net 提供对XML 的支持。

4.New delete** 与**malloc free** 的联系与区别**?**

答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分派的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.

5.有哪几种情况只能用intialization list** 而不能用**assignment?**

1.当类中具有const、reference成员变量,对象成员;基类的构造函数都需要初始化。

2.当基类有带参构造,子类就应当声明一个将参数传递给基类构造函数的途径。3.当基类派生子类对象时,就要对基类数据成员等初始化。

(1)

#include<iostream> using namespace std;

class A {private: const int a; // const 成员 const int b; // const 成员 public: A(int i, int j) : a(i), b(j) { }// 必须在这里初始化 };

int main() { A a(1,2); return 0; }

(2)

#include<iostream> using namespace std;

class A { private: int x1; public: A(int i) { // 只有一个带参的构造函数 x1 = i; } }; class B : public A { private: int x2; public: B(int i) : A(i + 10) { // 必须在这里初始化 x2 = i; } };

int main() { B b(2); return 0; }

6.struct** class 的区别

答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其他方面是功能相称的。从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺少封装和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固的封装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少的方法并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应当使用 struct 关键字,否则,你应当使用 class 关键字。

7.软件开发五个重要step是什么?**

a需求分析: 分析并拟定用户需求,用采用适当的模型规范地表述这一需求,形成分析模型为要解决的现实世界中的事物建立抽象建模。 b系统设计: 设计阶段:拟定系统如何实现所需的功能----采用适当的数据结构+控制逻辑,将分析模型细化。 c编码实现: 选定一种适当的编程语言,编码实现上述的设计,并在开发过程中引入测试,完善整个解决方案。 d测试阶段 e系统维护

你在开发软件的时候,这5个step分别占用的时间比例是多少?

8.make file文件的作用是什么?

Make file是一种包含构建项目规则的文本文件,通常用于编译源代码文件以生成可执行程序、库文件或其他构建目标。它是与构建工具make一起使用的,make根据Makefile中的规则来管理项目的编译和构建过程。Makefile包含了文件之间的依赖关系以及如何将源代码转换为可执行文件的步骤。通过执行make命令,可以根据Makefile中定义的规则自动构建项目,确保只编译已更改的文件,从而提高效率。

9.UNIX显示文件夹中,文件名的命令是什么?能使文件内容显示在屏幕的命令是什么 ?
  • 要显示文件夹中的文件名,可以使用ls命令。例如,要列出当前目录中的文件和子目录,可以在终端中运行以下命令:

    ls
  • 要将文件的内容显示在屏幕上,可以使用cat命令或其他类似的命令。例如,要查看文件的内容,可以运行以下命令:

    cat 文件名

    其中,"文件名"是要查看的文件的名称。

如果希望在终端中分页查看文件内容,还可以使用命令moreless,例如:

more 文件名或less 文件名

这些命令允许您逐页查看文件内容,适用于较大的文件。

10.static有什么用途?(请至少说明两种)**

答:在C语言中,static重要定义全局静态变量,定义局部静态变量,定义静态函数

1.定义全局静态变量 :在全局变量前面加上关键字static,该全局变量变成了全局静态变量。全局静态变量有以下特点:

(1) 在全局数据区内分派内存

(2) 假如没有初始化,其默认值为0

(3) 该变量在本文件内从定义开始到文件结束可见

2.定义局部静态变量:在局部静态变量前面加上关键字static,该局部变量便成了静态局部变量。静态局部变量有以下特点:

(1) 该变量在全局数据区分派内存

(2) 假如不显示初始化,那么将被隐式初始化为0

(3) 它始终驻留在全局数据区,直到程序运营结束

(4) 其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。

3.定义静态函数:在函数的返回类型加上static关键字,函数即被定义成静态函数。静态函数有以下特点:

(1) 静态函数只能在本源文件中使用

(2) 在文件作用域中声明的inline函数默认为static

说明:静态函数只是一个普通的全局函数,只但是受static限制,他只能在文件坐在的编译单位内使用,不能呢个在其他编译单位内使用。

在C++语言中新增了两种作用:定义静态数据成员或静态函数成员

1.定义静态数据成员。

静态数据成员有如下特点:

(1) 内存分派:在程序的全局数据区分派

(2) 初始化和定义:静态数据成员定义时要分派空间,所以不能在类声明中初始化。

2.静态成员函数。

静态成员函数与类相联系,不与类的对象相联系。静态成员函数不能访问非静态数据成员。因素很简朴,非静态数据成员属于特定的类实例,静态成员函数重要用于对静态数据成员的操作。

(1) 静态成员函数没有this指针。

11.关键字static的作用是什么?**

这个简朴的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:

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

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

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

大多数应试者能对的回答第一部分,一部分能对的回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺陷,由于他显然不懂得本地化数据和代码范围的好处和重要性。

一方面static的最重要功能是隐藏,另一方面由于static变量存放在静态存储区,所以它具有持久性和默认值0。

12.不能做switch()的参数类型是:**

答 、switch的参数不能为实型。

由于switch后面只能带自动转换为整形(涉及整形)的类型,比如字符型char,unsigned int等,实数型不能自动转换为整形.可以手动强转实数型(int)double,但是会导致精度的丢失.假如后面要对实数型做选择的话,可以乘以10的倍数,然后进行选择,这样不会丢失精度.但是这样的话就要靠你去手动的控制乘以多少了

13.对于一个频繁使用的短小函数,在C语言中应用什么实现,在C++中应用什么实现?

答 、c用宏定义,c++用inline,内联函数是指用inline关键字修饰的函数

14.预解决器标记#error的目的是什么?

程序遇到可预判的错误的结果不崩溃报错

15.关键字const是什么含意?**

我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应当非常熟悉const能做什么和不能做什么.假如你从没有读到那篇文章,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个对的的答案。(假如你想知道更具体的答案,仔细读一下Saks的文章吧。)假如应试者能对的回答这个问题,我将问他一个附加的问题:下面的声明都是什么意思?

const int a;

int const a;

const int *a;

int * const a;

int const * a const;

前两个的作用是同样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针是不可修改的)。假如应试者能对的回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你也许会问,即使不用关键字 const,也还是能很容易写出功能对的的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:

1). 关键字const的作用是为给读你代码的人传达非常有用的信息,事实上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。假如你曾花很多时间清理其它人留下的垃圾,你就会不久学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)

2). 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。

3). 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的*参*数,*防止其被无意的代码修改。简而言之,这样可以减少bug的出现

16.关键字volatile有什么含意

一个定义为volatile的变量是说这变量也许会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

17.C++中的class和struct的区别**

从语法上,在C++中(只讨论C++中)。class和struct做类型定义时只有两点区别: (一)默认继承权限。假如不明确指定,来自class的继承按照private继承解决,来自struct的继承按照public继承解决;(也就是说,类的继承默认私有,结构体默认公有) (二)成员的默认访问权限。class的成员默认是private权限,struct默认是public权限。 除了这两点,class和struct基本就是一个东西。语法上没有任何其它区别。

不能由于学过C就总觉得连C++中struct和class都区别很大,下面列举的说明也许比较无聊,由于struct和class本来就是基本同样的东西,无需多说。但这些说明也许有助于澄清一些常见的关于struct和class的错误结识: (1)都可以有成员函数;涉及各类构造函数,析构函数,重载的运算符,友元类,友元结构,友元函数,虚函数,纯虚函数,静态函数; (2)都可以有一大堆public/private/protected修饰符在里边; (3)虽然这种风格不再被提倡,但语法上两者都可以使用大括号的方式初始化:

A a = {1, 2, 3};不管A是个struct还是个class,前提是这个类/结构足够简朴,比如所有的成员都是public的,所有的成员都是简朴类型,没有显式声明的构造函数。 (4)都可以进行复杂的继承甚至多重继承,一个struct可以继承自一个class,反之亦可;一个struct可以同时继承5个class和5个struct,虽然这样做不太好。 (5)假如说class的设计需要注意OO的原则和风格,那么没任何理由说设计struct就不需要注意。 (6)再次说明,以上所有说法都是指在C++语言中,至于在C里的情况,C里是主线没有“class”,而C的struct从主线上也只是个包装数据的语法机制。 ---------------------------------------------------------------

最后,作为语言的两个关键字,除去定义类型时有上述区别之外,此外尚有一点点:“class”这个关键字还用于定义模板参数就像“typename”。但关键字“struct”不用于定义模板参数。

关于使用大括号初始化

  class和struct假如定义了构造函数的话,都不能用大括号进行初始化

  假如没有定义构造函数,struct可以用大括号初始化。

  假如没有定义构造函数,且所有成员变量全是public的话,可以用大括号初始化。

  关于默认访问权限

  class中默认的成员访问权限是private的,而struct中则是public的

  关于继承方式

  class继承默认是private继承,而struct继承默认是public继承。

  关于模版

  在模版中,类型参数前面可以使用class或typename,假如使用struct,则含义不同,struct后面跟的是“non-type template parameter”,而class或typename后面跟的是类型参数。

class中有个默认的this指针,struct没有 不同点:构造函数,析构函数 this 指针

18.CVS是什么**

cvs(Concurrent Version System) 是一个版本控制系统。使用它,可以记录下你的源文件的历史。

例如,修改软件时也许会不知不觉混进一些 bug,并且也许过了很久你才会察觉到它们的存在。有了 cvs,你可以很容易地恢复旧版本,并从中看出到底是哪个修改导致了这个 bug。有时这是很有用的。

CVS服务器端对每个文件维护着一个修订号,每次对文件的更新,都会使得文件的修订号加1。在客户端中也对每个文件维护着一个修订号,CVS通过这两个修订号的关系,来进行Update,Commit和发现冲突等操作操作

19.vector 和 list的区别?

vector内部使用数组,访问速度快,但是删除数据比较耗性能 list内部使用链表,访问速度慢,但是删除数据比较快

20.在函数后面加个const是怎么理解的?

在函数后面加个const一般在类的成员函数中使用,表示这个函数不修改数据成员的值

另:void set_prt_val(int val)const {ptr= val}理解:指针ptr指向的内容不是类的数据成员,所以这可这么写:ptr = val;但这个指针在这个函数中不能修改。假如写成这样:ptr = &i(假设i是此外一个整形变量)就不对了,由于改变了指针的内容。

21.请说出const与#define** 相比**,有何优点?**

答案:Const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以防止意外的变动,能提高程序的健壮性。

1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换也许会产生意料不到的错误。 2) 有些集成化的调试工具(如Visual Studio、Eclipse)可以对const 常量进行调试,但是不能对宏常量进行调试。

22.sizeof 和 strlen 的区别(常考)

(1)sizeof是关键字,strlen是库函数 (2)sizeof测的是类型/变量/表达式占用实际字节大小,单位: byte strlen测的字符串/字符指针/字符数组中字符的个数,单位: 个 (3)**sizeof遇到'\0'不会停止,同时包含'\0'占用字节 strlen遇到'\0'会停止,不会统计'\0'这个字符的个数

23.explicit关键字

explicit一般加在有参构造函数之前,防止隐式传参,如果不加的话,可能会造成,创建对象时,即调用有参构造,又调用拷贝构造函数

编译
1.main** 函数执行以前,还会执行什么代码?

答案:全局对象的构造函数会在main 函数之前执行。

一、main结束 不代表整个进程结束 (1)全局对象的构造函数会在main 函数之前执行, 全局对象的析构函数会在main函数之后执行; 用atexit注册的函数也会在main之后执行。 (2)一些全局变量、对象和静态变量、对象的空间分派和赋初值就是在执行main函数之前,而main函数执行完后,还要去执行一些诸如释放空间、释放资源使用权等操作 (3)进程启动后,要执行一些初始化代码(如设立环境变量等),然后跳转到main执行。全局对象的构造也在main之前。 二、main()之后执行的代码,用atexit注册的函数也会在main之后执行

2.**如何判断一段程序是由C** 编译程序还是由**C++编译程序编译的?**

答案: #ifdef __cplusplus cout<<"c++"; #else cout<<"c"; #endif

(1)假如是要你的代码在编译时发现编译器类型,就判断cplusplus或STDC_宏,通常许多编译器尚有其他编译标志宏,#ifdef __cplusplus cout<<"c++";#else cout<<"c";#endif假如要判断已经编译的代码的编译类型,就用nm查一下输出函数符号是否和函数名相同。(相同为c,不同为c++。详解见下面)(2)简朴是说,由于c语言是没有重载函数的概念的,所以c编译器编译的程序里,所有函数只有函数名相应的入口。而由于c++语言有重载函数 的概念,假如只有函数名相应的入口,则会出现混淆,所以c++编译器编译的程序,应当是函数名+参数类型列表相应到入口。 注意,由于mian函数是整个程序的入口,所以mian是不能有重载的,所以,假如一个程序只有main函数,是无法确认是c还是c++编译器编译的可以通过nm来查看函数名入口如一个函数int foo(int i, float j) c编译的程序通过nm查看foo 0x567xxxxxx (地址)c++编译程序,通过nm查看foo(int, float) 0x567xxxxxx此外,假如要在c++编译器里使用通过c编译的目的文献,必须告知c++编译器,我使用的函数是c风格的,不需要列出参数列表的,这样c++编译才干对的的连接

3.h头文件中的ifndef/define/endif** 的作用?

答:防止该头文件被反复引用。

4.在C++** 程序中调用被**C** 编译器编译后的函数,为什么要加**extern** “**C”?**

C++语言支持函数重载,C语言不支持函数重载。C++提供了C连接互换指定符号extern “C”解决名字匹配问题。

一方面,作为extern是C/C++语言中表白函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用

通常,在模块的头文献中对本模块提供应其它模块引用的函数和全局变量以关键字extern声明。例如,假如模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文献即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目的代码中找到此函数

extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同

4.什么是预编译,何时需要预编译?**

预编译又称为预解决,是做些代码文本的替换工作。解决#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,重要解决#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。

c编译系统在对程序进行通常的编译之前,先进行预解决。c提供的预解决功能重要有以下三种:1)宏定义 2)文件包含 3)条件编译

1、总是使用不经常改动的大型代码体。 2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。

5.互换两个数的宏定义**

互换两个参数值的宏定义为:. #define SWAP(a,b) (a)=(a)+(b);(b)=(a)-(b);(a)=(a)-(b);

6.C语言编译流程

预处理 gcc -E main.c -o main.i (1)展开头文件 (2)宏替换 (3)删除注释或者注释变空行 (4)处理条件编译的逻辑 编译 gcc -S main.i -o main.s (1)逐行检查代码的语法或者语义错误 (2)将源程序转换为汇编语言 汇编 gcc -c main.s -o main.o (1)将汇编语言转换为机器语言【二进制代码】 链接 gcc main.o -o main (1)链接其它文件 (2)链接库文件

gcc -I include -I util main.c mymath.c -o app

编译选项

-E 预处理 -S 编译 -c 汇编 -o 指定输出的目标文件的路径/名称 -I 指定头文件所在的文件夹的路径 -l 指定链接库的文件名称 -L 指定链接库所在的文件夹的路径

7.头文件包含时,#include后双引号和尖括号的区别?

#include <文件名> 编译系统优先在系统指定的路径下(系统库)搜索头文件; #include "文件名" 编译系统优先在当前路径下搜索头文件

命名风格

_s: 安全函数 strcpy_s strcat_s

_t: 使用typedef关键字创建出新类型 size_t time_t

g_: 全局变量 global

s_: 静态变量 static

m_: 成员变量 member

数字表示的有多少位 typedef signed char int8_t; typedef short int16_t; typedef int int32_t; typedef long long int64_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned long long uint64_t;

常用的头文件(C语言)记忆

输入和输出的函数 printf scanf puts gets putchar getchar sprintf sscanf fprintf fscanf ... / #include <stdio.h> / malloc free atoi itoa rand srand / #include <stdlib.h> / _Bool类型的一个使用 bool ture false / #include <stdbool.h> / 函数的不定长参数 / #include <stdarg.h> / typedef signed char int8_t; typedef unsigned char uint8_t; typedef short int16_t; typedef unsigned short uint16_t; typedef int int32_t; typedef unsigned uint32_t; typedef long long int64_t; typedef unsigned long long uint64_t;

#define INT8_MIN (-128) #define INT16_MIN (-32768) #define INT32_MIN (-2147483647 - 1) #define INT64_MIN (-9223372036854775807LL - 1) #define INT8_MAX 127 #define INT16_MAX 32767 #define INT32_MAX 2147483647 #define INT64_MAX 9223372036854775807LL #define UINT8_MAX 255 #define UINT16_MAX 65535 #define UINT32_MAX 0xffffffffU #define UINT64_MAX 0xffffffffffffffffULL / #include <stdint.h> / string相关函数: strlen strcpy strncpy strcat strtok strcmp strchr strstr strrchr 内存相关函数:memcpy memset / #include <string.h> / 最大值和最小值的一些宏 / #include <limits.h> / assert 宏 / #include <assert.h> / min max abs pow sqrt / #include <math.h> / time localtime strftime / #include <time.h> / int isalpha(int _C); 是否为字母 int isupper(int _C); 是否为大写字母 int islower(int _C); 是否为小写字母 int isdigit(int _C); 是否为数字 int isalnum(int _C); 是否为数字和字母 int toupper(int _C); 转为大写字母 int tolower(int _C); 转为小写字母 */ #include <ctype.h>

编码规范

好代码质量保证优先原则

(1)正确性,指程序要实现设计要求的功能。 (2)简洁性,指程序易于理解并且易于实现。 (3)可维护性,指程序被修改的能力,包括纠错、改进、新需求或功能规格变化的适应能力。 (4)可靠性,指程序在给定时间间隔和环境条件下,按设计要求成功运行程序的概率。 (5)代码可测试性,指软件发现故障并隔离、定位故障的能力,以及在一定的时间和成本前提下,进行测试设计、测试执行的能力。 (6)代码性能高效,指是尽可能少地占用系统资源,包括内存和执行时间。 (7)可移植性,指为了在原来设计的特定环境之外运行,对系统进行修改的能力。

编写代码的规则

(1)函数的长度不应该大于50行 (2)函数的参数不应该多于5个 (3)用宏定义表达式时,要使用完备的括号 (4)函数的功能尽量单一,一个函数仅完成一个功能 (5)每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口 (6)头文件应当自包含 (7)一行代码尽量保证在120个字符 (8)申请内存后应该判断是否成功【如:malloc函数,new关键字】 (9)函数的参数是指针时,使用前应该进行判空处理 (10)常规的配置信息禁止采用硬编码的形式,应该使用配置文件或数据库的方式,比如密码、端口号、IP等 (11)日志文件中避免打印绝对路径、敏感信息【如:工号、密码、token、session等】 (12)避免使用魔鬼数字

常见的错误

链接错误

常见的链接错误:(link错误或者提示ld.exe错误【这种是clion或者linux上常见的错误提示】) (1)只找到函数的声明,无法找到函数的实现 (2)只找到extern声明的全局变量或者全局函数,没有找到定义的地方 (3)类中声明了静态成员变量,并没有在类外进行初始化,使用时就会出现该问题 (4)只找到对应的库的头文件中的函数声明,未找到对应的库文件

语言类
1.C++有哪些性质(面向对象特点)**

封装,继承和多态。

在面向对象程序设计语言中,封装是运用可重用成分构造软件系统的特性,它不仅支持系统的可重用性,并且尚有助于提高系统的可扩充性;消息传递可以实现发送一个通用的消息而调用不同的方法;封装是实现信息隐蔽的一种技术,其目的是使类的定义和实现分离。

2.c和c++有什么区别?

c语言是注重面向过程,而c++是面向对象的一个开发模型

c语言是作为结构化和模块化的语言,在处理较小规模的程序时,比较得心应手,适用于底层小规模的开发,但是当问题比较复杂,程序规模比较大的时(就是有成千上万的业务量),需要高度的抽象和建模时,c语言显得力不从心,这时就要用c++面向对象的思想给它抽出来,建立出来一个抽象建模,来完成实现复杂的上层应用开发

在应用领域 项目中,既要求效率又要建模和高度抽象,就选c++

系统层软件开发--c++语言本身的高效

例如:c语言在写一个变量的时候,要考虑怎么分配和释放,而其他语言像java,pathyh就不需要考虑这些(语言本身自动回收了),而c语言不是,为的就是帮助你内存怎么使用怎么分配,没有任何的多余的分配,没有多余的容错,保持并提高效率。包括像在堆上申请内存,同时还要考虑到释放等等。

c++ 涉及范围:应用领域 系统层软件开发 服务器程序开发 游戏,网络,分布式,云计算(效率与建模) 科学计算

3.C和C++** 的共同点?不同之处?

C是面向过程的语言,C++是面向对象,但是不全是。JAVA是纯面向对象

在语言层面上,C++和JAVA都基本同样,只是JAVA语言去掉了指针,多继承等容易犯错的东东.现在的JAVA不单纯的是指语言,它是一个开发平台的通称,Java的通用性好,可以跨平台直接移植,只要有安装Java虚拟机(JVM)就可以了。开发的效率高。生成的机器码效率没有汇编和C的高。

(1)C语言是面向过程的,c++是面向对象的,两者都是高级语言 (2)C语言是类型检查不严格的语言,c++是类型检查严格的语言 (3)C语言申请堆区空间使用的malloc,释放使用的是free C++语言建议申请堆区空间使用的new,释放使用的是delete,数组delete[] (4) C语言的布尔类型使用的是_Bool这个关键字 C++语言的布尔类型使用的是bool这个关键字 (5) C语言的空指针类型使用的是NULL这个宏 C++语言的空指针类型使用的是nullptr这个关键字

4.. C++是不是类型安全的?**

答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。

5.*比较C++中的4种类型转换方式?**

类型转换有c风格的,当然尚有c++风格的。c风格的转换的格式很简朴(TYPE)EXPRESSION,但是c风格的类型转换有不少的缺陷,有的时候用c风格的转换是不合适的,由于它可以在任意类型之间转换,比如你可以把一个指向const对象的指针转换成指向非const对象的指针,把一个指向基类对象的指针转换成指向一个派生类对象的指针,这两种转换之间的差别是巨大的,但是传统的c语言风格的类型转换没有区分这些。尚有一个缺陷就是,c风格的转换不容易查找,他由一个括号加上一个标记符组成,而这样的东西在c++程序里一大堆。所以c++为了克服这些缺陷,引进了4新的类型转换操作符,他们是1.static_cast 2.const_cast 3.dynamic_cast 4.reinterpret_cast.

1.static_cast

最常用的类型转换符,在正常状况下的类型转换,如把int转换为float,如:int i;float f; f=(float)i;或者f=static_cast<float>(i);

2.const_cast

用于取出const属性,把const类型的指针变为非const类型的指针,如:const int *fun(int x,int y){}  int *ptr=const_cast<int *>(fun(2.3))

3.dynamic_cast

该操作符用于运营时检查该转换是否类型安全,但只在多态类型时合法,即该类至少具有一个虚拟方法。dynamic_cast与static_cast具有相同的基本语法,dynamic_cast重要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是同样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。如:

class C

{//…C没有虚拟函数 };

class T{   //…} int main() {  dynamic_cast<T*> (new C);//错误 }此时如改为以下则是合法的: class C

{ public:   virtual void m() {};// C现在是 多态 }

4.reinterpret_cast

interpret是解释的意思,reinterpret即为重新解释,此标记符的意思即为数据的二进制形式重新解释,但是不改变其值。如:int i; char *ptr="hello freind!"; i=reinterpret_cast<int>(ptr);这个转换方式很少使用。

const_cast:常量转换 只能用于指针和引用类型,主要用于去掉const限制,也可以用于加const限制 static_cast:静态转换 静态类型转换,编译时期做的,不能用于无关类型之间的转换【不能将 int* 转换为 char 】【不能将结构体)(类)类型指针转换为intreinterpret_cast:重写解释转换 类型不安全的转换方式,是对static_cast的补充 如:Student* 转换为 int *,使用static_cast是不可以的,reinterpret_cast是可以的 dynamic_cast: 动态转换 只能用于含有虚函数的类 当发生多态的时候,向上转型和向下转型都是安全的 没有多态时,子转父是安全的,父转子是不安全的,转换指针失败,结果是空指针,转换引用失败,结果抛出bad_cast异常

6.C++中为什么用模板类。

答:(1)可用来创建动态增长和减小的数据结构 (2)它是类型无关的,因此具有很高的可复用性。 (3)它在编译时而不是运营时检查数据类型,保证了类型安全 (4)它是平台无关的,可移植性 (5)可用于基本数据类型

7.c和c++分文件编写时的规定

(1) 开发一个源程序,都应该对应一个同名的头文件 (2) 每一个源程序,都应该自包含自己的同名头文件,

8.c++11的新特性(重点)

(1)使用nullptr关键字代替宏NULL (2) auto关键字,类型自动推导 (3) 增强for循环 (4) 列表初始化 (5) 智能指针 (6) 匿名函数 (7) 正则 (8) stl里面一些新的容器,unorder_map/unorder_set/tuple (9) thread库 (10) constexpr / noexcept / override

面向对象
1.面向对象的三个基本特性,并简朴叙述之?**

\1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)

\2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。

\3. 多态:是将父对象设立成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简朴的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

多态通俗的说就是多种形态,比如我们在买票的时候不同人群买票的价格和方式是不同的,比如军人可以优先买票,学生票半价等

用一句话说就是执行一个事物会发生不同的情况,在代码中就是不同继承关系的类对象对一个函数进行操作,会有不同的结果产生。

多态是不同继承关系的类对象,去调用同一函数,产生不同的行为(基类对象和派生类对象去调用同一函数所产生的效果不同)。那么在继承中构成多态的条件有两个1.被调用的函数必须是虚函数,且必须完成虚函数的重写。2.必须通过基类的指针或者引用去调用虚函数。

静态多态:静态多态也称为编译时多态,即在编译时决定调用哪个方法,函数重载和运算符重载属于静态多态,复用函数名(编译期多态);

在函数编译时期就确定程序的行为,这种就是静态多态,比如函数重载就是一个静态多态的例子。

动态多态:动态多态就是在程序运行的过程才会确定程序的具体行为,调用不同的函数。派生类和虚函数实现运行时多态(运行期多态

多态的三个存在条件:**

1、有继承关系; 2、子类重写父类中的虚函数。

3.动态多态使用: 父类指针或引用指向子类对象,就像我们的eat()函数一样

原理就是在执行某个函数时,传进的对象不同,会根据对象模型中的虚函数指针找到对应的虚函数表,在虚函数表中找到对应要执行的虚函数指针,进而找到该虚函数并执行**

2.**多态的作用?**

重要是两个:

\1. 隐藏实现细节,使得代码可以模块化;扩展代码模块,实现代码重用;

\2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的对的调用。

好处:灵活,提高代码的可复用性

3.继承

c++既支持单继承又支持多继承

公有继承:【权限不变】 父类是什么权限,子类也是什么权限 受保护继承: 公有权限变为受保护的权限,其它不变 私有继承: 全是私有的权限 默认继承权限【私有继承】 (1)如果要创建一个子类的对象,肯定是要调用父类的构造函数,如果父类中没有对应的构造函数,则会报错。 (2)子类中如果没有明确说明要调用父类某个构造函数,则默认是调用父类的无参构造。 菱形继承: A B C 解决方法:父类中继承时,使用虚继承的方式 D 问题:基类的成员就会在派生类中出现多份,造成访问基类的成员不明确的问题【二义性问题】

4.**继承的优缺陷。**

类继承是在编译时刻静态定义的,且可直接使用,类继承可以较方便地改变父类的实现。但是类继承也有一些局限性之处。一方面,由于继承在编译时刻就定义了,所以无法在运营时刻改变从父类继承的实现。更糟的是,父类通常至少定义了子类的部分行为,父类的任何改变都也许影响子类的行为。假如继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

(待补充)

二:进阶

4.函数类
1.**子类析构时要调用父类的析构函数吗?**

析构函数调用的顺序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经所有销毁了定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数;JAVA无析构函数深拷贝和浅拷贝

2.**多态,虚函数,纯虚函数**

多态:是对于不同对象接受相同消息时产生不同的动作。C++的多态性具体体现在运营和编译两个方面:在程序运营时的多态性通过继承和虚函数来体现;

在程序编译时多态性体现在函数和运算符的重载上

虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。

纯虚函数:虚函数后面 = 0; 特点: (1)如果一个类中有纯虚函数,则这个类将变成抽象类【类的接口化处理】 (2)抽象类无法实例化对象,只能通过子类去重写纯虚函数,来创建对象

纯虚函数的作用:在基类中为其派生类保存一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在 纯虚函数不具有函数的功能,一般不能直接被调用。

从基类继承来的纯虚函数,在派生类中仍是虚函数。假如一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。

抽象类中不仅涉及纯虚函数,也可涉及虚函数。l抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运营时多态性。

3.虚析构函数【重点】

当子类中有成员变量是指针时,同时父类的指针指向了子类的对象,当delete父类的指针时,如果父类的析构函数不是虚析构函数,则只会调用父类的析构函数,不会调用子类的析构函数,这样会造成内存泄漏(所以要将父类的析构函数变为虚析构)

提问?(重点)

析构函数可以是纯虚函数吗?

可以的,但是这个析构函数依然需要在类外进行实现

存在纯虚析构函数的类是抽象类吗?

是的,也是不能直接实例化对象的

3.论述含参数的宏与函数的优缺陷。**

函数是内置的,执行效率高,速度快。宏可以自己定制,灵活性较大,但执行速度相对慢。

1.**函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简朴的字符替换。**

2.**函数调用是在程序运营时解决的,分派临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分派内存单元,不进行值的传递解决,也没有“返回值”的概念。**

3.**对函数中的实参和形参都要定义类型,两者的类型规定一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,** 展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。

4.**调用函数只可得到一个返回值,而用宏可以设法得到几个结果。**

5.**使用宏次数多时,宏展开后源程序长,由于每展开一次都使程序增长,而函数调用不使源程序变长。**

6.**宏替换不占运营时间,只占编译时间;而函数调用则占运营时间(分派单元、保存现场、值传递、返回)。**

一般来说,用宏来代表简短的表达式比较合适。内联函数和宏很类似,而区别在于,宏是由预解决器对宏进行替代,而内联函数是通过编译器控制来实现的。并且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏同样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数同样来调用内联函数,而不必紧张会产生于解决宏的一些问题。 当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,假如,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率同样了。

内联函数是不能为虚函数的,但样子上写成了内联的,即隐含的内联方式。在某种情况下,虽然有些函数我们声明为了所谓“内联”方式,但有时系统也会把它当作普通的函数来解决,这里的虚函数也同样,虽然同样被声明为了所谓

“内联”方式。但系统会把它当然非内联的方式来解决。

4.如何定义和实现一个类的成员函数为回调函数?

假如试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。通过查询资料发现,其错误是普通的C++成员函数都隐含了一个传递函数作为参数,亦即“this”指针,C++通过传递this指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数却-有不同的数据成员。由于this指针的作用,使得将一个CALL-BACK型的成员函数作为回调函数安装时就会由于隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。要解决这一问题的关键就是不让this指针起作用,通过采用以下两种典型技术可以解决在C++中使用回调函数所碰到的问题。这种方法具有通用性,适合于任何C++。

  1). 不使用成员函数,为了访问类的成员变量,可以使用友元操作符(friend),在C++中将该函数说明为类的友元即可。

  2). 使用静态成员函数,静态成员函数不使用this指针作为隐含参数,这样就可以作为回调函数了。静态成员函数具有两大特点:其一,可以在没有类实例的情况下使用;其二,只能访问静态成员变量和静态成员函数,不能访问非静态成员变量和非静态成员函数。由于在C++中使用类成员函数作为回调函数的目的就是为了访问所有的成员变量和成员函数,假如做不到这一点将不具有实际意义。解决的办法也很简朴,就是使用一个静态类指针作为类成员,通过在类创建时初始化该静态指针,如pThis=this,然后在回调函数中通过该静态指针就可以访问所有成员变量和成员函数了。这种解决办法合用于只有一个类实例的情况,由于多个类实例将共享静态类成员和静态成员函数,这就导致静态指针指向最后创建的类实例。为了避免这种情况,可以使用回调函数的一个参数来传递this指针,从而实现数据成员共享。这种方法稍稍麻烦,这里就不再赘述。

5.什么函数不能声明为虚函数:

一个类中将所有的成员函数都尽也许地设立为虚函数总是有益的。

设立虚函数须注意:

1:只有类的成员函数才干说明为虚函数;

2:静态成员函数不能是虚函数;

3:内联函数不能为虚函数;

4:构造函数不能是虚函数;

5:析构函数可以是虚函数,并且通常声明为虚函数。

类里面“定义”的

6.纯虚函数是如何实现的?在编译原理上讲一下?

在类内部添加一个虚拟函数表指针,该指针指向一个虚拟函数表,该虚拟函数表包含了所有的虚拟函数的入口地址,每个类的虚拟函数表都不

同样,在运营阶段可以循此脉络找到自己的函数入口。

纯虚函数相称于占位符,先在虚函数表中占一个位置由派生类实现后再把真正的函数指针填进去。除此之外和普通的虚函数没什么区别。

7.抽象类为什么不能实例化?

抽象类中的纯虚函数没有具体的实现,所以没办法实例化。

8.解释一下构造函数

构造函数:构造函数产生对象:建立对象时,必须提供与构造函数形参一致的实参:其基本格式有以下两种: 1.类名 对象名 //自动调用,无参构造函数 2.类名 对象名(实参); //自动调用,与之对应的有参构造函数 缺省的构造函数 1.缺省构造函数的种类 <1.系统自动产生的构造函数 类名(){} <2.用户定义的无参构造函数 <3.用户定义的所有参数都有缺省值的构造函数 2.用缺省构造函数创建对象的格式:类名 对象名; 3.注意事项 <1. 只有用户没有定义构造函数时,系统才会自动产生构造函数; <2. 类中可能没有缺省的构造函数 <3.一个类中最多只能有一个缺省的构造函数 构造函数和new运算符 1.用new运算符产生动态对象 <1.产生单个对象(可以初始化) <2.产生数组对象(不能初始化) 构造函数的形参不能全部为缺省值 目的:为了避免在使用构造函数时出现二义性。如果构造函数的所有形参都有默认值,那么在创建对象时就无法确定应该使用哪个构造函数,从而导致二义性的产生。

9.什么是深浅拷贝?

首先:拷贝构造函数:拷贝构造函数是一种特殊的构造函数,其形参为本类的对象的引用。

作用:从一个已有对象,来初始化新创建的对象。 如果用户没有为类声明拷贝构造函数,则系统自动生成一个拷贝构造函数 析构函数:在对象离开生命周期前做的最后一件事,用系统自动调用。用于撤消 对象的成员函数(对象释放前被系统自动调用),做一些清理工作。

注意事项:1.析构函数的名称由运算符“~”与类名组成; 2.析构函数无参数,无返回值; 3.析构函数不可以重载,即一个类只有一个析构函数; 4.如果用户没有定义,系统会自动生成默认析构函数: 5.类名::~类名() {} 6.当类中用new运算符分配了动态空间时,必须定义析构函数,并在函数体中用delete运算符释放动态存储空间。

定义格式构造函数与析构函数的调用过程:在创建对象时,调用构造函数;在撤消对象时,调用析构函数;所以它们的调用过程(顺序)通常是相反的。 深浅拷贝 浅拷贝:从已存在对象来创建新的对象,系统会调用拷贝构造函数,如果用户没有定义拷贝构造函数,则系统会自动生成默认的拷贝构造函数,进行值拷贝

对于大部分类浅拷贝是没有问题的,但对于类成员变量中有指针,文件、句柄等情况下,默认的浅拷贝就有可能让类无法正常工作(内存的二次释放),会导致程序崩溃 深拷贝:让新产生的对象,其成员变量对资源的引用操持独立,相互之间不受影响,仅保持“值”相同。 自定义拷贝:通过自定义拷贝构造函数,解决的多个对象“值”相同的问题,让每个对象保持独立,相互之间不受影响。

10.c++中的空类占几个字节

空类占用一个字节 原因:因为空类也可以实例化对象,那么这个对象就要在内存中占用字节,所以c++语言就为了方便能够找到这个对象,分配了一个字节

11.类中默认函数的有哪些

(1)无参构造【当你自定义一个无参构造或者有参构造时,系统不会自动给你提供一个无参构造】 (2)拷贝构造 (3)析构函数 (4)赋值运算符重载函数 (5)移动构造函数 (6)移动赋值运算符重载函数

12.构造函数和析构函数的调用顺序(先构造的后析构)

构造函数顺序: 先调用父类构造 -> 成员变量的构造 —> 子类的构造 析构函数顺序: 先调用子类的析构 -> 成员变量的析构 -> 先调用父类构造

13.什么是内联函数,为什么要使用内联函数

内联函数: 当一个函数的前面使用inline关键字进行修饰,他就是一个内联函数 好处: 如果内联函数可以被展开,那么那会将自己的代码逻辑直接插入到调用本函数地方去,省去了函数调用的栈消耗【进栈和出栈】

14.内联函数和宏的区别

(1)宏一定会被展开,而内联函数取决于编译器的能力,当然我们一般要求内联函数【逻辑简单,代码量小】 (2)宏没有类型检查,而内联函数有类型检查 (3)在调试的过程中,宏无法进行调试,内联函数他可以像普通函数一样进行调试

15.常成员函数?

(1)常函数:就是在普通的成员函数定义时后面加一个const修饰符,同时如果有函数声明,声明的位置也要加const (2)作用:在常函数的内部不能修改其成员变量 如果非要在常函数修改成员变量,则需要在成员变量的前面加上一个mutable关键字 (3)使用方法: 常函数只能调用常函数 普通函数可以调用普通成员函数,当然可以调用常函数 常对象只能调用常函数 常引用只能调用常函数 普通对象可以调用普通成员函数,常函数 普通引用可以调用普通成员函数,常函数

16.静态成员函数?

(1)静态成员函数:就是在普通的成员函数定义时前面加一个static修饰符,同时如果有函数声明,声明的位置需要加static关键字,而实现的地方不能加static (2)作用: 是属于这个类所有的对象共有的,不是属于某个对象独有的 (3)使用方法: 静态成员函数只能调用静态成员函数 静态成员函数不能调用非静态成员函数【普通的成员函数】 普通成员函数可以调用静态成员函数 普通的成员是可以调用静态成员函数【建议使用类名访问】 类名可以直接访问静态成员函数 类名是不可以直接访问非静态成员函数【普通的成员函数】

17.静态成员变量?

(1)普通成员变量前加static关键字 (2)这个变量属于这个类的所有对象共享,不是属于某个对象独有的 (3)静态成员变量在类内定义,类外初始化【初始化的时候不能加static关键字】 特例: 如果非要将静态成员变量初始化放到类内,则需要使用const/constexpr进行修饰 static const int mm = 10010; (4)使用方法 类名和普通成员都可以直接访问静态成员函数【建议使用类名访问】

18.匿名函数?

匿名函数:lambda表达式【本质就是一个无名的函数】 语法格式: 捕获列表 mutable —> 返回值类型 {函数体}; 捕获列表的形式: []: 默认不捕获任何匿名函数外的变量

[=]  捕获匿名函数外的所有变量,但是是值捕获,相当于把外面的变量复制了一份到匿名函数中, 

并且该变量默认是不能修改,如果非要修改,可以增加mutable关键字

[&]  捕获匿名函数外的所有变量,但是是引用捕获,可以修改外部变量的值 

[m]  仅仅捕获匿名函数外的m这个变量。是值捕获,但不能修改,如果非要修改,可以增加mutable关键字 

[&m]  仅仅捕获匿名函数外的m这个变量的引用。是引用捕获,可以修改外部变量的值 

[m, n]: m,n都捕获, 值捕获 ​ [&m, &n]: m,n都捕获, 引用捕获

[&, m]  m捕获值,其余捕获的是引用 

[=, &m]  其它捕获为值,m捕获的引用 

[this]  捕获匿名函数外的this这个指针所对应的成员变量和成员函数 

优点: ​ (1)代码简短 ​ (2)函数随时定义,用完就释放 ​ 用法: ​ 回调函数,做算法库中的一些函数的参数

19.回调函数

回调函数: 函数指针做函数的形参,把这个函数指针就叫做回调函数 作用: 将功能上有差异的地方,可以用回调函数来代替【回调函数可以解决代码差异化的部分】 增强代码的扩展性 实现方式: (1)函数指针 (2)匿名函数 (3)仿函数

STL库相关:
1. []运算符重载函数和at函数的区别

(1)[]如果越界后,程序会直接崩溃 (2)at函数越界后,抛出out_of_range异常,可以进行捕获,保证程序不会立即崩溃

2.string或者vector的扩容机制

(1)一般采用1.5倍或者2.0倍的扩容方式 (2) 先申请一个更长的内存块,然后将原有的内存块中的内容拷贝到新的内存块中,释放原有内存块,同时当前类的成员变量指向这个新的内存块。

3.map和unordered_map的区别

map和unordered_map都是C++ STL中的关联容器,它们都提供了键值对的存储和查询功能,但是它们的实现方式不同,因此在使用时也有一些区别。

map是基于红黑树实现的,它按照键值的大小进行排序,因此在使用时需要保证键值类型支持比较操作符,同时插入和查找的时间复杂度都是O(log n)级别的。由于map是有序的,因此可以使用lower_bound和upper_bound等函数进行范围查找。

unordered_map是基于哈希表实现的,它不会对键值进行排序,因此插入和查找的时间复杂度是O(1)级别的。但由于哈希表的实现需要解决哈希冲突的问题,因此在使用时需要保证键值类型支持哈希函数和相等比较函数。unordered_map也提供了一些与map类似的函数,如find、erase等。 综合来说,如果需要在键值对中进行排序或范围查找,可以选择使用map;如果需要快速的插入和查找,可以选择使用unordered_map。

重载
1.重载(overload)和重写(overried,有的书也叫做“覆盖”)重定义 的区别?**

常考的题目。从定义上来说:

重载:是指允许存在多个同名函数,函数名相同,参数列表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。同时const也能重载,常函数和普通成员函数之间也可以发生重载

重写:是指子类重新定义父类虚函数的方法。

重定义:【隐藏的特定】 子类中存在与父类同名的函数(虚函数除外), 那么子类的这个函数将会把父类的同名函数隐藏 如果想非要调用父类同名函数,可以使用三种方法: (1) 子类对象转换为父类的引用,可以通过此引用去调用父类的函数 (2) 子类对象地址赋值给父类的指针 (3) 子类对象通过父类的类名去访问父类的函数 eg: aa.BB::show()

从实现原理上来说:

重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称也许是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经拟定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!

重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法拟定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运营期绑定的(晚绑定)。

简单来说:可以描述为

重载 两个函数在同一个作用域中,函数名相同,函数的参数不同。

重写(覆盖) 两个函数分别在基类和派生类的作用域中,函数名,参数和返回值都必须相同(协变是例外),两个函数必须是虚函数

重定义(隐藏) 两个函数分别在基类和派生类的作用域中,函数名相同,如果他们不构成重写就是重定义。

2.类成员函数的重载、覆盖和隐藏区别?**

答案:a.成员函数被重载的特性: (1)相同的范围(在同一个类中); (2)函数名字相同; (3)参数不同; (4)virtual 关键字可有可无。 b.覆盖是指派生类函数覆盖基类函数,特性是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有virtual 关键字。 c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下: (1)假如派生类的函数与基类的函数同名,但是参数不同。此时,不管有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。 (2)假如派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

3.有关重载函数**

返回值类型不同构不成重载 参数参数顺序不同能构成重载

c++函数同名不同返回值不算重载!函数重载是忽略返回值类型的。

--------------------------------------------- 成员函数被重载的特性有: 1) 相同的范围(在同一个类中); 2) 函数名字相同; 3) 参数不同; 4) virtual关键字可有可无。

5) 成员函数中 有无const (函数后面) 也可判断是否重载

4.运算符重载

前置++ ++i 后置++ i++ []运算符重载【下标】 << 输出运算符重载 friend ostream& operator<<(ostream& out, const 类名& ins);

输入运算符重载 friend istream& operator>>(istream& in, 类名& ins); -> 指针对象访问运算符

  • 解引用访问运算符 () 运算符重载 = 运算符重载,返回值是引用,return *this; += 运算符重载,返回值是引用,return *this;

  • 运算符重载,返回值是类型(对象),return 新对象;

5.如果将类作为map容器的key使用,则这个类必须实现什么运算符重载?

map容器的特点:是key不能重复,并且key是有序的,底层使用红黑树来实现。 为了保证有序性,则这个类必须实现比较运算符重载

引用
1.什么是“引用”?申明和使用“引用”要注意哪些问题?**

答:引用就是某个目的变量的“别名”(alias),相应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,牢记要对其进行初始化。引用声明完毕后,相称于目的变量名有两个名称,即该目的原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表达该引用名是目的变量名的一个别名,它自身不是一种数据类型,因此引用自身不占存储单元,系统也不给引用分派存储单元。不能建立数组的引用。

2.**将“引用”作为函数参数有哪些特点?**

(1)传递引用给函数与传递指针的效果是同样的。这时,被调函数的形参就成为本来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目的对象(在主调函数中)的操作。

(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分派存储单元,形参变量是实参变量的副本;假如传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

(3)使用指针作为函数的参数虽然也能达成与使用引用的效果,但是,在被调函数中同样要给形参分派存储单元,且需要反复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清楚。

3.**在什么时候需要使用“常引用”?** 

假如既要运用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标记符 &引用名=目的变量名;

例1

int a ; const int &ra=a; ra=1; //错误 a=1; //对的

例2

string foo( ); void bar(string & s);

那么下面的表达式将是非法的:

bar(foo( )); bar("hello world");

因素在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。引用型参数应当在能被定义为const的情况下,尽量定义为const 。

4.**将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?**

格式:类型标记符 &函数名(形参列表及类型说明){ //函数体 }

好处:在内存中不产生被返回值的副本;(注意:正是由于这点因素,所以返回一个局部变量的引用是不可取的。由于随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error! 注意事项:

(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。重要因素是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

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

(3)可以返回类成员的引用,但最佳是const。这条原则可以参照Effective C++[1]的Item 30。重要因素是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值经常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。假如其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

(4)流操作符重载返回值申明为“引用”的作用:

流操作符<<和>>,这两个操作符经常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应当是一个仍然支持这两个操作符的流引用。可选的其它方案涉及:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符事实上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的因素吧。 赋值操作符=。这个操作符象流操作符同样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。

例3

#i nclude <iostream.h> int &put(int n); int vals[10]; int error=-1; void main() { put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10; put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20; cout<<vals[0]; cout<<vals[9]; } int &put(int n) { if (n>=0 && n<=9 ) return vals[n]; else { cout<<"subscript error"; return error; } }

(5)在此外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23具体的讨论了这个问题。重要因素是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案涉及:返回一个对象、返回一个局部变量的引用,返回一个new分派的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又由于((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

5.**“引用”与多态的关系?**

引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。例4

Class A; Class B : Class A{...}; B b; A& ref = b;

6.**“引用”与指针的区别是什么?**

指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用自身就是目的变量的别名,对引用的操作就是对目的变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。

7.**什么时候需要“引用”?**

流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。以上 2-8 参考:

指针:
1..**简述数组与指针的区别?**

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。 (1)修改内容上的差别 char a[] = “hello”; a[0] = ‘X’; char *p = “world”; // 注意p 指向常量字符串 p[0] = ‘X’; // 编译器不能发现该错误,运营时错误 (2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。 char a[] = "hello world"; char *p = a; cout<< sizeof(a) << endl; // 12 字节 cout<< sizeof(p) << endl; // 4 字节 计算数组和指针的内存容量 void Func(char a[100]) { cout<< sizeof(a) << endl; // 4 字节而不是100 字节 }

2.引用与指针有什么区别?**

相同点:1. 都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

区别:

1.指针是一个实体,而引用仅是个别名;

2.引用使用时无需解引用(*),指针需要解引用;

3.引用只能在定义时被初始化一次,之后不可变;

4.指针可变;并且 指针可以不初始化,引用必须初始化

5.引用没有 const,指针有 const;

6.引用不能为空,指针可认为空;

7.“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针自身(所指向的变量或对象的地址)的大小;

8.指针和引用的自增(++)运算意义不同样;

9.从内存分派上看:程序为指针变量分派内存区域,而引用不需要分派内存区域。

10.指针占用内存【32位 4个字节,64位 8个字节】,引用不占用内存 11.指针可以有多级,引用不可以

3. Itearator 各指针的区别**

游标和指针

我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是事实上,游标是通过重载一元的”*”和”->”来从容器中间接地返回一个值。将这些值存储在容器中并不是一个好主意,由于每当一个新值添加到容器中或者有一个值从容器中删除,这些值就会失效。在某种限度上,游标可以看作是句柄(handle)。通常情况下游标(iterator)的类型可以有所变化,这样容器也会有几种不同方式的转变:

iterator——对于除了vector以外的其他任何容器,你可以通过这种游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用“++”操作符。而不能使用“--”或“+=”操作符。而对于vector这一种容器,你可以使用“+=”、“—”、“++”、“-=”中的任何一种操作符和“<”、“<=”、“>”、“>=”、“==”、“!=”等比较运算符。

4.常量指针和指针常量?

常量指针:const char * / char const * 【字符串常量】 指针所指向的内存块中的值不能被修改 指针常量:char * const 【数组名,this指针】 指针的指向不能被修改 常量指针常量:const char * const

5.智能指针(重点必问)

智能指针四种: auto_ptr:(废弃) (1) 将一个类的对象的指针作为auto_ptr的成员变量,在本类的析构方法中释放该指针 (借用栈的特性,出了作用域后会自动释放局部变量,对于对象而言,会自动调用析构函数) (2) 重载了*和->运算符 (3) 为了避免重复的释放的问题,在拷贝构造方法和赋值运算符重载的实现中,将原有对象的成员变量置为nullptr unique_ptr: 独占的 (1) 同一对象的指针只能被一个独占型智能指针所管理 (2) 他的内部实现是 将拷贝构造函数和赋值运算符重载函数删除 shared_ptr: 共享的 (1)shared_ptr允许多个shared_ptr对象指向同一个被管理对象 (2)shared_ptr是通过引用计数来管理对象的,拷贝一个shared_ptr,计数器都会递增, 离开作用域时,计数器会递减,如果计数器变为0,它就会自动释放自己所管理的对象。 weak_ptr: (1)不能独立使用,必配合shared_ptr使用 (2)weak_ptr是为了解决shared_ptr的引用成环问题, 使用weak_ptr时引用计数不会自增

shared_ptr 会使引用计数增加 weak_ptr 不会使引用计数增加

shared_ptr和make_shared的区别 shared_ptr会进行两次内存申请,一次是被管理对象的内存申请,一次是计数器的内存申请。如果在这个两个过程中出现异常的话,则可能出现被管理对象的内存不会被智能指针所管理。从而可能会发生内存泄漏 make_shared实质就是将内存申请和被智能指针所管理的逻辑放到一块,从而避免两次申请内存过程中出错的问题 unique_ptr和make_unique

---结构体联合体
1.**结构与联合有何区别?**

(1). 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。 (2). 对于联合的不同成员赋值, 将会对其它成员重写, 本来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

2.**面关于“联合”的题目的输出?**

a)

#i nclude <stdio.h> union { int i; char x[2]; }a;

void main() { a.x[0] = 10; a.x[1] = 1; printf("%d",a.i);} 答案:266 (低位低地址,高位高地址,内存占用情况是Ox010A)

b)

main() { union{ /定义一个联合/ int i; struct{ /在联合中定义一个结构/ char first; char second; }half; }number; number.i=0x4241; /联合成员赋值/ printf("%c%cn", number.half.first, mumber.half.second); number.half.first='a'; /联合中结构成员赋值/ number.half.second='b'; printf("%xn", number.i); getch(); } 答案: AB (0x41相应'A',是低位;Ox42相应'B',是高位)

6261 (number.i和number.half共用一块地址空间)

3.**关联、聚合(Aggregation)以及组合(Composition)的区别?**

涉及到UML中的一些概念:关联是表达两个类的一般性联系,比如“学生”和“老师”就是一种关联关系;聚合表达has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表达聚合关系:从实现的角度讲,聚合可以表达为:

class A {...} class B { A* a; .....}

而组合表达contains-a的关系,关联性强于聚合:组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表达组合关系:实现的形式是:

class A{...} class B{ A a; ...}

4.联合体【共用体】和结构体的区别(常考)

联合体【union】: 多个成员共有一块内存,以成员占用最大内存为总内存 结构体【struct】: 每个成员占用的内存都是独立的,同时因为有字节对齐要求,需要计算所有的成员占用实际内存之和

5.c++中类和结构体区别

类的默认权限是私有的 结构体的默认权限是公有的

内存/分区/变量的使用
1.介绍一下内存分区

(1)代码区 二进制的代码 (2)全局/静态区 全局变量,静态变量 (3)常量文本区 字符串常量 (4)栈区 局部变量,函数的形参,函数的返回值 (5)堆区 malloc申请出的内存

编译后就已经确定:【size命令查看编译后的目标程序】 .text段 代码区 .bss段 未初始化或者初始化为0的全局变量和静态变量【全局变量中const修饰的没有赋值的变量】 .data段 已初始化【非0】的全局变量和静态变量 .rodata段: read only 常量区【已经初始化的用const修饰的全局变量和静态变量】 运行时: 栈区 堆区 反汇编:【objdump反汇编指令】【将二进制程序转换为汇编语言】 objdump -D main > main.s【汇编文件】 【可以通过反汇编出来的汇编文件,查看内存的分布情况】

2.描述**内存分派方式以及它们的区别?**

1) 从静态存储区域分派。内存在程序编译的时候就已经分派好,这块内存在程序的整个运营期间都存在。例如全局变量,static 变量。 2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分派运算内置于解决器的指令集。 3) 从堆上分派,亦称动态内存分派。程序在运营的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

3.多重继承的内存分派问题:

比如有class A : public class B, public class C {} 那么A的内存结构大体是怎么样的? 这个是compiler-dependent的, 不同的实现其细节也许不同。假如不考虑有虚函数、虚继承的话就相称简朴;否则的话,相称复杂。可以参考《进一步探索C++对象模型

4.解释局部变量、全局变量和静态变量的含义。**

按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。

按作用域分,全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分派一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。

5.解释堆和栈的区别。

1、 栈区(stack)— 由编译器自动分派释放 ,存放函数的参数值,局部变量的值等。其 操作方式类似于数据结构中的栈。 2、堆区(heap) — 一般由程序员分派释放, 若程序员不释放,程序结束时也许由OS回 收 。注意它与数据结构中的堆是两回事,分派方式倒是类似于链表,呵呵。 3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的 全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另 一块区域。 - 程序结束后由系统释放。 4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放 5、程序代码区—存放函数体的二进制代码。

C**语言程序通过编译连接后形成编译、连接后形成的二进制映像文件由栈,堆,数据段(由三部分部分组成:只读数据段,已经初始化读写数据段,未初始化数据段即BBS)和代码段组成**

1.栈区(stack):由编译器自动分派释放,存放函数的参数值,局部变量等值。其操作方式类似于数据结构中的栈。

2.堆区(heap):一般由程序员分派释放,若程序员不释放,则也许会引起内存泄漏。注堆和数据结构中的堆栈不同样,其类是与链表。

3.程序代码区:存放函数体的二进制代码。

4.数据段:由三部分组成:

<1. 只读数据段:

只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。一般是const修饰的变量以及程序中使用的文字常量一般会存放在只读数据段中。

<2. 已初始化的读写数据段:

已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并且有初值,以供程序运营时读写。在程序中一般为已经初始化的全局变量,已经初始化的静态局部变量(static修饰的已经初始化的变量)

<3. 未初始化段(BSS):

未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运营之前不需要占用存储器的空间。与读写数据段类似,它也属于静态数据区。但是该段中数据没有通过初始化。未初始化数据段只有在运营的初始化阶段才会产生,因此它的大小不会影响目的文件的大小。在程序中一般是没有初始化的全局变量和没有初始化的静态局部变量。

6.堆栈溢出一般是由什么因素导致的?**

答 、1.没有回收垃圾资源 2.层次太深的递归调用

7.局部变量能否和全局变量重名?**

答、能,局部会屏蔽全局。要用全局变量,需要使用"::"

局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内

8.如何引用一个已经定义过的全局变量?**

答 、可以用引用头文件的方式,也可以用extern关键字,假如用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,假如你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错

9.全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?**

答 、可以,在不同的C文件中以static形式来声明同名全局变量。

可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会犯错

10.static** 全局变量、局部变量、函数与普通全局变量、局部变量、函数

static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?

答 、全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量自身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。

从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。

static函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应当说明为内部函数(static),内部函数应当在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应当在一个头文件中说明,要使用这些函数的源文件要包含这个头文件

static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;

static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;

static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝

程序的局部变量存在于栈(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。

11.关于内存对齐的问题以及sizof()的输出

答:编译器自动对齐的因素:为了提高程序的性能,数据结构(特别是栈)应当尽也许地在自然边界上对齐。因素在于,为了访问未对齐的内存,解决器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

数据结构,算法
1..如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针)

struct node { char val; node* next;} bool check(const node* head) {} //return false : 无环;true: 有环一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,假如有环的话两者必然重合,反之亦然): bool check(const node* head) { if(head==NULL) return false; node *low=head, *fast=head->next; while(fast!=NULL && fast->next!=NULL) { low=low->next; fast=fast->next->next; if(low==fast) return true; } return false; }

2.用两个栈实现一个队列的功能?规定给出算法和思绪!**

答 、设2个栈为A,B, 一开始均为空.

入队:

将新元素push入栈A;

出队:

(1)判断栈B是否为空;

(2)假如不为空,则将栈A中所有元素依次pop出并push到栈B;

(3)将栈B的栈顶元素pop出;

这样实现的队列入队和出队的平摊复杂度都还是O(1), 比上面的几种方法要好。

3.插入排序和选择排序**

插入排序基本思想:(假定从大到小排序)依次从后面拿一个数和前面已经排好序的数进行比较,比较的过程是从已经排好序的数中最后一个数开始比较,假如比这个数,继续往前面比较,直到找到比它大的数,然后就放在它的后面,假如一直没有找到,肯定这个数已经比较到了第一个数,那就放到第一个数的前面。那么一般情况下,对于采用插入排序法去排序的一组数,可以先选 取第一个数做为已经排好序的一组数。然后把第二个放到对的位置。

选择排序(Selection Sort)是一种简朴直观的排序算法。它的工作原理如下。一方面在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。

4.语言中结构化程序设计的三种基本控制结构**

顺序结构 选择结构 循环结构

首先:顺序、选择和循环是三种基本的控制结构,用于控制程序的执行流程。

1.顺序结构

顺序结构是指程序按照代码的顺序逐行执行,没有任何条件或循环的限制。代码按照从上到下的顺序执行,每一行代码都会被依次执行,直到程序结束。顺序结构是最简单、最基本的控制结构

2.选择结构

选择结构:选择结构用于根据条件选择性地执行不同的代码块。在C语言中,常见的选择结构是if语句和switch语句。 if语句:根据条件的真假来决定是否执行某段代码。如果条件为真,则执行if代码块中的语句;如果条件为假,则跳过if代码块。 示例: int a = 10; if (a > 5) { printf("a大于5\n"); } else { printf("a不大于5\n"); } switch语句:如果没有匹配的case标签,则执行default代码块(可选)。 示例: int day = 3; switch (day) { case 1: printf("星期一\n"); break; case 2: printf("星期二\n"); break; case 3: printf("星期三\n"); break; default: printf("未知\n"); break; }

3.循环结构

循环结构用于重复执行一段代码,直到满足特定的条件才停止。在C语言中,常见的循环结构有while循环、do-while循环和for循环。 while循环 在循环开始之前,先判断条件是否为真,如果为真,则执行循环体中的代码,然后再次判断条件,直到条件为假时停止循环。 示例: int i = 0; while (i < 5) { printf("i = %d\n", i); i++; } do-while循环 先执行一次循环体中的代码,然后再判断条件是否为真,如果为真,则继续执行循环体中的代码,直到条件为假时停止循环。 示例: int i = 0; do { printf("i = %d\n", i); i++; } while (i < 5); for循环 在循环开始之前,先执行一次初始化语句,然后判断条件是否为真,如果为真,则执行循环体中的代码,然后执行循环后的增量语句,再次判断条件,直到条件为假时停止循环。 示例: for (int i = 0; i < 5; i++) { printf("i = %d\n", i); }

总结回答:顺序结构按照代码的顺序逐行执行; 选择结构根据条件选择性地执行不同的代码块; 循环结构重复执行一段代码,直到满足特定的条件才停止。

5.三种基本的数据模型**

按照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型

6.冒泡排序算法的时间复杂度是什么?**

答 、O(n^2)

设计模式
1.设计模式:工厂模式 和 单例模式 介绍一下?

工程模式即将对象创建过程封装即为工厂模式。

单例模式即整个类只有一个对象,并且不允许显示创建。而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的 “单一实例” 的需求时才可使用。

数据库
1.数据库与T-SQL语言**

关系数据库是表的集合,它是由一个或多个关系模式定义。SQL语言中的数据定义功能涉及对数据库、基本表、视图、索引的定义。

2.关系模型的基本概念**

关系数据库以关系模型为基础,它有以下三部分组成: ●数据结构——模型所操作的对象、类型的集合 ●完整性规则——保证数据有效、对的的约束条件 ●数据操作——对模型对象所允许执行的操作方式 关系(Relation)是一个由行和列组成的二维表格,表中的每一行是一条记录(Record),每一列是记录的一个字段(Field)。表中的每一条记录必须是互斥的,字段的值必须具有原子性。

3.SQL语言概述**

SQL(结构化查询语言)是关系数据库语言的一种国际标准,它是一种非过程化的语言。通过编写SQL,我们可以实现对关系数据库的所有操作。 ●数据定义语言(DDL)——建立和管理数据库对象 ●数据操纵语言(DML)——用来查询与更新数据 ●数据控制语言(DCL)——控制数据的安全性

起来是一个很简朴的问题,每一个使用过RDBMS的人都会有一个概念。

事务解决系统的典型特点是具有ACID特性。ACID指的是Atomic(原子的)、Consistent(一致的)、Isolated(隔离的)以及Durable(连续的),它们代表着事务解决应当具有的四个特性:

原子性:组成事务解决的语句形成了一个逻辑单元,不能只执行其中的一部分

一致性:在事务解决执行之前和之后,数据是一致的。

隔离性:一个事务解决对另一个事务解决没有影响。

连续性:当事务解决成功执行到结束的时候,其效果在数据库中被永久纪录下来。

网络通信
1.请你分别画出OSI的七层网络结构图和TCP/IP的五层结构图。**

应用层:为应用程序提供服务

表达层:解决在两个通信系统中互换信息的表达方式

会话层:负责维护两个结点间会话连接的建立、管理和终止,以及数据互换

传输层:向用户提供可靠的端到端服务。UDP TCP协议。

网络层:通过路由选择算法为分组通过通信子网选择最适当的途径,以及实现拥塞控制、网络互联等功能。数据传输单元是分组。IP地址,路由器,IP协议。

数据链路层:在物理层提供的服务基础上,数据链路层在通信的实体间建立数据链路连接,传输一帧为单位的数据包(,并采用差错控制与流量控制方法,使有差错的物理线路变成无差错的数据链路。)

物理层:传输比特流。传输单元是比特。调制解调器。

2.请你具体地解释一下IP协议的定义,在哪个层上面?重要有什么作用?TCP与UDP呢**

网络层。

作用:IP协议定义了互联网中数据传输的基本规则,主要负责将数据包从源地址传输到目的地址,同时也负责处理数据包的路由问题。是TCP/IP协议栈中的重要组成部分。

TCP和UDP是基于IP协议的两种传输层协议。

TCP协议(Transmission Control Protocol)是一种可靠的、面向连接的协议,它在数据传输时会建立连接、进行数据传输、确认数据接收等步骤,确保数据的可靠传输。

而UDP协议(User Datagram Protocol)则是一种不可靠的、无连接的协议,它在数据传输时不会进行连接建立、数据确认等步骤,数据传输速度快,但不保证数据的可靠传输。

TCP和UDP协议在应用层之间进行数据传输,它们的作用是为应用层提供数据传输服务。TCP协议适用于对数据传输可靠性要求较高的应用场景,如文件传输、电子邮件等;

而UDP协议适用于对数据传输速度要求较高、但对数据传输可靠性要求较低的应用场景,如实时音视频传输、网络游戏等。

3.请问互换机和路由器各自的实现原理是什么?分别在哪个层次上面实现的?**

互换机(Switch)是一种网络设备,它主要用于在局域网中转发数据包。

互换机的实现原理是基于MAC地址的转发,当数据包到达互换机时,互换机会查找目的MAC地址,然后将数据包转发到对应的端口上,从而实现了局域网内部的数据交换。互换机的工作原理是在数据链路层上实现的。 路由器(Router)是一种网络设备,它主要用于在不同的网络之间转发数据包。路由器的实现原理是基于IP地址的转发,当数据包到达路由器时,路由器会查找目的IP地址,然后根据路由表将数据包转发到对应的网络上,从而实现了不同网络之间的数据交换。路由器的工作原理是在网络层上实现的

4..Windows程序的入口是哪里?写出Windows消息机制的流程。**

入口点是**WinMain函数.Windows消息机制的流程:**

1.Windows**中有一个系统消息队列,对于每一个正在执行的Windows应用程序,系统为其建立一个“消息队列”,即应用程序队列,用来存放该程序也许创建的各种窗口的消息。应用程序中具有一段称作“消息循环”的代码,用来从消息队列中检索这些消息并把它们分发到相应的窗口函数中。**

2.Windows**为当前执行的每个Windows程序维护一个「消息队列」。在发生输入事件之后,Windows将事件转换为一个「消息」并将消息放入程序的消息队列中。程序通过执行一块称之为「消息循环」的程序代码从消息队列中取出消息:**

3.Internet采用哪种网络协议?该协议的重要层次结构?**

答 、tcp/ip 应用层/传输层/网络层/数据链路层/物理层

4.Internet物理地址和IP地址转换采用什么协议?**

答 、ARP (Address Resolution Protocol)(地址解析协议)

5.winsock建立连接的重要实现环节?

答:TCP:服务器端:1.socket()建立套接字,2将套接字绑定到本地地址和端口上,绑定(bind)3.将套接字设为监听模式,准备接受客户端,监听(listen);4.等待客户端请求到来,请求到来后,连接请求,并返回一个新的相应此连接的套接字,accept()5.用返回的套接字和客户端进行通讯(send/recv);6.返回并等待另一客户请求。7.关闭套接字。 客户端:1.socket()建立套接字2.向服务器发出连接请求,(connect)2。和服务器进行通信,send()和recv(),在套接字上写读数据,直至数据互换完毕;4closesocket()关闭套接字。 UDP:1服务器端:1.创建套接字(socekt)2.将套接字绑定到本地地址和端口上(bind);3.等待接受数据(recvfrom);4.closesocket()关闭套接字。

客户端:1.创建套接字(socekt)2,向服务器端发送数据(sendto)3.closesocket()关闭套接字。

6.动态连接库的两种方式?

答:调用一个DLL中的函数有两种方法: 1.载入时动态链接(load-time dynamic linking),模块非常明确调用某个导出函数,使得他们就像本地函数同样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。 2.运营时动态链接(run-time dynamic linking),运营时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了。

线程/进程
1.进程和线程的区别**

什么是进程(Process):普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:

进程间是独立的,这表现在内存空间,上下文环境;线程运营在进程空间内。 一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处在进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不可以同时执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。

线程是指进程内的一个执行单元,也是进程内的可调度实体.与进程的区别: (1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间; (2)进程是资源分派和拥有的单位,同一个进程内的线程共享进程的资源 (3)线程是解决器调度的基本单位,但进程不是. (4)两者均可并发执行.

2.进程间通信类型:

(1)环境变量、文件描述符 一般Unix环境下的父进程执行fork(),生成的子进程拥有了父进程当前设立的环境变量以及文件描述符;由于通信是一个单向的、一次性的通信,随后的父进程以及子进程后续的内容不能再能共享;

(2)命令行参数 大多数用户都使用过ShellExec相关的命令,此API可以打开新的进程,并可以通过接口里的输入参数进行信息共享;同样,他也是一个单项、一次性的通信;

(3)管道 使用文件和写方式访问公用的数据结构;管道分为匿名管道和命名管道,前者是用作关联进程间用,后者为无关联的进程使用;前者通过文件描述符或文件句柄提供对命名管道的访问,后者需要知道管道名称才干读写管道;一般来讲,读写的内容是字节流,需要转换为故意义的结构才故意义;

(4)共享内存 进程需要可以被其他进程访问浏览的进程块;进程间共享内存的关系与函数间共享全局变量的关系类似

(5)DDE 动态数据交互

线程间通信类型:

(1)全局数据;

(2)全局变量;

(3)全局数据结构;

(4)线程间通信的参数:pThread_create这类API接口中的参数

3..CSingleLock是干什么的。

答:同步多个线程对一个数据类的同时访问

静态库和动态库的使用

使用静态库的步骤: (1)指定头文件的目录(重点:一定是目录路径不是文件路径) vs中的: 项目 >> 属性 >> c/c++ >> 常规 >> 附加包含目录 clion中的: include_directorys() linux中:(如果有多个头文件的目录) gcc/g++ -I 头文件的目录 -I 头文件的目录 (2)指定库文件(.lib文件或者.a文件)的目录(重点:一定是目录路径不是文件路径) vs中的: 项目 >> 属性 >> 链接器 >> 常规 >> 附加库目录 clion中的: link_directorys() linux中: gcc/g++ -L 库文件的目录 -L 库文件的目录 (3)指定要进行链接的库文件(文件名称【.lib或者.a文件】) vs中的: 项目 >> 属性 >> 链接器 >> 输入 >> 附加依赖项 clion中的: target_link_libraries() linux中: gcc/g++ -l 具体的库名 -l 具体的库名

使用动态库的步骤: (1)指定头文件的目录(重点:一定是目录路径不是文件路径) vs中的: 项目 >> 属性 >> c/c++ >> 常规 >> 附加包含目录 clion中的: include_directorys() linux中: gcc/g++ -I (2)指定库文件(.lib文件或者.a文件)的目录(重点:一定是目录路径不是文件路径) vs中的: 项目 >> 属性 >> 链接器 >> 常规 >> 附加库目录 clion中的: link_directorys() linux中: gcc/g++ -L (3)指定要进行链接的库文件(文件名称【.lib或者.a文件】) vs中的: 项目 >> 属性 >> 链接器 >> 输入 >> 附加依赖项(lib和main.lib后缀两个) clion中的: target_link_libraries() linux中: gcc/g++ -l

windows: 最后,需要将dll文件和exe文件放在同一级目录(建议这种方式) 或者将dll文件复制到C:/Windows/System32 C:/Windows/SysWOW64

linux:找不到动态库文件的解决办法(ldd命令可以查看当前的应用程序是否缺少某个动态库) 永久设置, 把export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径, 设置到∼/.bashrc文件中, 然后在执行下列三种办法之一:

执行 . ~/.bashrc 使配置文件生效(第一个.后面有一个空格) 执行source ~/.bashrc配置文件生效 退出当前终端, 然后再次登陆也可以使配置文件生效

静态库和动态库的区别:

静态库的表现形式在Windows中常以.lib结尾,在Linux系统中以.a结尾。 优点:发布时只需发布.exe文件,因为库已经集成到可执行文件中,运行时不再依赖库。 缺点:静态库编译到目标文件中,导致目录文件较大,同时,如果后续升级静态库后必须重新编译目标文件。 动态库的表现形式在Windows中常以.dll结尾,在Linux系统中以.so结尾。 优点: 1、更加节省内存并减少页面交换; 2、DLL文件与EXE文件独立。 3、节约磁盘空间 缺点: 1、容易造成缺失 2、运行速度相比静态库慢 3、容易出现不兼容

常见部分笔试题:
1.求下面函数的返回值(微软)
int func(x)

{ int countx = 0; while(x) { countx ++; x = x&(x-1); } return countx; }

假定x = 9999。 答案:8

思绪:将x转化为2进制,看具有的1的个数。

2.面关于“联合”的题目的输出?**

a)

#i nclude <stdio.h> union { int i; char x[2]; }a;

void main() { a.x[0] = 10; a.x[1] = 1; printf("%d",a.i);} 答案:266 (低位低地址,高位高地址,内存占用情况是Ox010A)

b)

main() { union{ /定义一个联合/ int i; struct{ /在联合中定义一个结构/ char first; char second; }half; }number; number.i=0x4241; /联合成员赋值/ printf("%c%cn", number.half.first, mumber.half.second); number.half.first='a'; /联合中结构成员赋值/ number.half.second='b'; printf("%xn", number.i); getch(); } 答案: AB (0x41相应'A',是低位;Ox42相应'B',是高位)

6261 (number.i和number.half共用一块地址空间)

3.分别写出BOOL,int,float,指针类型的变量a** 与**“零”的比较语句。**

答案: BOOL : if ( !a ) or if(a) int : if ( a == 0) float : const EXPRESSION EXP = 0.000001 if ( a < EXP && a >-EXP)

float a; const float eps=0.000001; if((a>=-eps) && (a<=eps) ) pointer : if ( a != NULL) or if(a == NULL)

4.求出两个数中的较大的一个;不用if*,?:;switch或其他比较判断**

There are two int variables: a and b, don’t use “if”, “? :”, “switch”or other judgement statements, find out the biggest one of the two numbers.

答案:( ( a + b ) + abs( a - b ) ) / 2

假如a>b,那么a-b>0,所以表达式就变成了(a+b+a-b)/2=(a+a)/2=a。 假如a<b,那么a-b<0,取绝对值变成-(a-b),所以表达式变成了(a+b-a+b)/2=(b+b)/2=b。 总的来说就是取a和b中的最大值max。

5.文件中有一组整数,规定排序后输出到另一个文件中**

答案:

#include <iostream>

#include <vector>

#include <fstream>

using namespace std;

void BubbleSort(vector<int>& array)

{

for (int i=0;i!=array.size();i++)

{

for (int j=array.size()-1;j!=i;j--)

{

if (array[j]<array[j-1])

{

swap(array[j],array[j-1]);

}

}

}

}

void swap(int* a,int* b)

{

int temp;

temp=*a;

a=b;

*b=temp;

}

void main()

{

vector<int> data;

ifstream in("c:\data.txt");

if (!in)

{

cout<<"file error!"<<endl;

exit(1);

}

int temp;

while(!in.eof())

{

in>>temp;

data.push_back(temp);

}

in.close();

BubbleSort(data);

ofstream out("c:\result.txt");

if (!out)

{

cout<<"file error!"<<endl;

exit(1);

}

for(int i=0;i<data.size();i++)

out<<data[i]<<" ";

out.close();

}

6.链表题:一个链表的结点结构//**与结构类型组合使用**

typedef struct tagMyStruct

{

int iNum;

long lLength;

} MyStruct;//(**此处MyStruct为结构类型别名)=>**

struct tagMyStruct

{

int iNum;

long lLength;

};//+

typedef struct tagMyStruct MyStruct;

//**结构中包含指向自己的指针用法**

typedef struct tagNode

{

char *pItem;

pNode pNext;

} *pNode;//=>error

//1)

typedef struct tagNode

{

char *pItem;

struct tagNode *pNext;

} *pNode;

//2)

typedef struct tagNode *pNode;

struct tagNode

{

char *pItem;

pNode pNext;

};

//3)**规范**

struct tagNode

{

char *pItem;

struct tagNode *pNext;

};

typedef struct tagNode *pNode;

struct Node { int data ; Node *next ; }; typedef struct Node Node ;

(1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel)

Node * ReverseList(Node *head) //链表逆序 { if ( head == NULL || head->next == NULL ) return head; Node *p1 = head ; Node *p2 = p1->next ; Node *p3 = p2->next ; p1->next = NULL ; while ( p3 != NULL ) { p2->next = p1 ; p1 = p2 ; p2 = p3 ; p3 = p3->next ; } p2->next = p1 ; head = p2 ; return head ; }

(2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表仍然有序。(保存所有结点,即便大小相同)

Node * Merge(Node *head1 , Node *head2) { if ( head1 == NULL) return head2 ; if ( head2 == NULL) return head1 ; Node *head = NULL ; Node *p1 = NULL; Node *p2 = NULL; if ( head1->data < head2->data ) { head = head1 ; p1 = head1->next; p2 = head2 ; } else { head = head2 ; p2 = head2->next ; p1 = head1 ; } Node *pcurrent = head ; while ( p1 != NULL && p2 != NULL) { if ( p1->data <= p2->data ) { pcurrent->next = p1 ; pcurrent = p1 ; p1 = p1->next ; } else { pcurrent->next = p2 ; pcurrent = p2 ; p2 = p2->next ; } } if ( p1 != NULL ) pcurrent->next = p1 ; if ( p2 != NULL ) pcurrent->next = p2 ; return head ; } (3)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表仍然有序,这次规定用递归方法进行。 (Autodesk) 答案: Node * MergeRecursive(Node *head1 , Node *head2) { if ( head1 == NULL ) return head2 ; if ( head2 == NULL) return head1 ; Node *head = NULL ; if ( head1->data < head2->data ) { head = head1 ; head->next = MergeRecursive(head1->next,head2); } else { head = head2 ; head->next = MergeRecursive(head1,head2->next); } return head ;

----------

\41. 分析一下这段程序的输出 (Autodesk) class B { public: B() { cout<<"default constructor"<<endl; } ~B() { cout<<"destructed"<<endl; } B(int i):data(i) //B(int) works as a converter ( int -> instance of B) { cout<<"constructed by parameter " << data <<endl; } private: int data; };

B Play( B b) { return b ; }

(1) results: int main(int argc, char* argv[]) constructed by parameter 5 { destructed B(5)形参析构 B t1 = Play(5); B t2 = Play(t1);   destructed t1形参析构 return 0;               destructed t2 注意顺序! } destructed t1

(2) results: int main(int argc, char* argv[]) constructed by parameter 5 { destructed B(5)形参析构 B t1 = Play(5); B t2 = Play(10);   constructed by parameter 10 return 0;               destructed B(10)形参析构 } destructed t2 注意顺序!

destructed t1

7.写一个函数找出一个整数数组中,第二大的数** (**microsoft)**

答案: const int MINNUMBER = -32767 ; int find_sec_max( int data[] , int count) { int maxnumber = data[0] ; int sec_max = MINNUMBER ; for ( int i = 1 ; i < count ; i++) { if ( data[i] > maxnumber ) { sec_max = maxnumber ; maxnumber = data[i] ; } else { if ( data[i] > sec_max ) sec_max = data[i] ; } } return sec_max ; }

8.写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。**

KMP算法效率最佳,时间复杂度是O(n+m),

#include <iostream>

int Search(char *Str,char *Sum){

int l1=strlen(Str);

int l2=strlen(Sum);

if (l1-l2<0) //假如子串大于字符串长度直接返回

return -1;

int i;

for (i=0;i<l1-l2;i++)

{

int m=i;

int j;

for (j=0;j<l2;j++)

{

if (Str[m]!=Sum[j]) //有不匹配字符直接跳出

break;

m++;

}

if (j==l2) //找到匹配,跳出循环

break;

}

return i<=l1-l2?i:-1;

}

void main(){

char Str[15]="abdedabjlfdf";

char Sum[4]="aj";

std::cout<<Search(Str,Sum);

getchar();

}

9.指针找错题**

分析这些面试题,自身包含很强的趣味性;而作为一名研发人员,通过对这些面试题的进一步剖析则可进一步增强自身的内功。   2.找错题 试题1: 以下是引用片段: void test1() //数组越界   {   char string[10];   char* str1 = "";   strcpy( string, str1 );   }   试题2:  以下是引用片段:  void test2()   {   char string[10], str1[10];   int i;   for(i=0; i<10; i++)   {   str1= 'a';   }   strcpy( string, str1 );   }   试题3:   以下是引用片段: void test3(char* str1)   {   char string[10];   if( strlen( str1 ) <= 10 )   {   strcpy( string, str1 );   }   }   解答:   试题1字符串str1需要11个字节才干存放下(涉及末尾的’\0’),而string只有10个字节的空间,strcpy会导致数组越界;对试题2,假如面试者指出字符数组str1不能在数组内结束可以给3分;假如面试者指出strcpy(string,str1)调用使得从 str1内存起复制到string内存起所复制的字节数具有不拟定性可以给7分,在此基础上指出库函数strcpy工作方式的给10分; 对试题3,if(strlen(str1) <= 10)应改为if(strlen(str1) <10),由于strlen的结果未记录’\0’所占用的1个字节。剖析:考核对基本功的掌握   (1)字符串以’\0’结尾;   (2)对数组越界把握的敏感度;   (3)库函数strcpy的工作方式,

10.假如编写一个标准strcpy函数**

总分值为10,下面给出几个不同得分的答案:2分 以下是引用片段: void strcpy( char *strDest, char strSrc )   {   while( (strDest++ = * strSrc++) != ‘\0’ );   }   4分 以下是引用片段:  void strcpy( char *strDest, const char strSrc )   //将源字符串加const,表白其为输入参数,加2分   {   while( (strDest++ = * strSrc++) != ‘\0’ );   }   7分 以下是引用片段: void strcpy(char *strDest, const char strSrc)   {   //对源地址和目的地址加非0断言,加3分   assert( (strDest != NULL) &&(strSrc != NULL) );   while( (strDest++ = * strSrc++) != ‘\0’ );   }   10分 以下是引用片段: //为了实现链式操作,将目的地址返回,加3分!   char * strcpy( char *strDest, const char *strSrc )   {   assert( (strDest != NULL) &&(strSrc != NULL) );   char address = strDest;   while( (strDest++ = * strSrc++) != ‘\0’ );   return address;   }   从2分到10分的几个答案我们可以清楚的看到,小小的strcpy居然暗藏着这么多玄机,真不是盖的!需要多么扎实的基本功才干写一个完美的strcpy啊!   (4)对strlen的掌握,它没有涉及字符串末尾的'\0'。   读者看了不同分值的strcpy版本,应当也可以写出一个10分的strlen函数了,完美的版本为: int strlen( const char str ) //输入参数const 以下是引用片段:  {   assert( strt != NULL ); //断言字符串地址非0   int len=0; //注,一定要初始化。   while( (str++) != '\0' )   {   len++;   }   return len;   }   试题4:以下是引用片段: void GetMemory( char *p )   {   p = (char *) malloc( 100 );   }   void Test( void )   {   char *str = NULL;   GetMemory( str );   strcpy( str, "hello world" );   printf( str );   }   试题5:  以下是引用片段: char *GetMemory( void )   {   char p[] = "hello world";   return p;   }   void Test( void )   {   char *str = NULL;   str = GetMemory();   printf( str );   }   试题6:以下是引用片段: void GetMemory( char p, int num )   {   *p = (char *) malloc( num );   }   void Test( void )   {   char *str = NULL;   GetMemory( &str, 100 );   strcpy( str, "hello" );   printf( str );   }   试题7**:以下是引用片段:  void Test( void )   {   char *str = (char *) malloc( 100 );   strcpy( str, "hello" );   free( str );   ... //省略的其它语句   }   解答:试题4传入中GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完   char *str = NULL;   GetMemory( str );   后的str仍然为NULL;试题5中   char p[] = "hello world";   return p;   的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。   试题6的GetMemory避免了试题4的问题,传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句 tiffanybracelets   *p = (char *) malloc( num );   后未判断内存是否申请成功,应加上:   if ( *p == NULL )   {   ...//进行申请内存失败解决

  }   试题7存在与试题6同样的问题,在执行   char *str = (char *) malloc(100);   后未进行内存是否申请成功的判断;此外,在free(str)后未置str为空,导致也许变成一个“野”指针,应加上:   str = NULL;   试题6的Test函数中也未对malloc的内存进行释放。   剖析:   试题4~7考察面试者对内存操作的理解限度,基本功扎实的面试者一般都能对的的回答其中50~60的错误。但是要完全解答对的,却也绝非易事。

  对内存操作的考察重要集中在:   (1)指针的理解;   (2)变量的生存期及作用范围;   (3)良好的动态内存申请和释放习惯。   再看看下面的一段程序有什么错误:   以下是引用片段: swap( int* p1,int* p2 )   {   int *p;   *p = *p1;   *p1 = *p2;   *p2 = p;   }   在swap函数中,p是一个“野”指针,有也许指向系统区,导致程序运营的崩溃。在VC++中DEBUG运营时提醒错误“Access Violation”。该程序应当改为 以下是引用片段: swap( int p1,int* p2 )   {   int p;   p = *p1;   *p1 = *p2;   *p2 = p;   }

11.String** 的具体实现

已知String类定义如下:

class String { public: String(const char *str = NULL); // 通用构造函数 String(const String &another); // 拷贝构造函数 ~ String(); // 析构函数 String & operater =(const String &rhs); // 赋值函数 private: char *m_data; // 用于保存字符串 };

尝试写出类的成员函数实现。

答案: String::String(const char *str) { if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断 { m_data = new char[1] ; m_data[0] = '\0' ; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data,str); }

}

String::String(const String &another)

{ m_data = new char[strlen(another.m_data) + 1]; strcpy(m_data,other.m_data); }

String& String::operator =(const String &rhs) { if ( this == &rhs) return *this ; delete []m_data; //删除本来的数据,新开一块内存 m_data = new char[strlen(rhs.m_data) + 1]; strcpy(m_data,rhs.m_data); return *this ; }

String::~String() { delete []m_data ; }

12.联想笔试题

1.设计函数 int atoi(char *s)。

#include<string.h>

int atoi(char *s)

{ int i,result=0,length;

length=strlen(s);

if(s[0] == '-') i=1; //若是负数,则从第一个字符开始计数

else i=0; //否则从0号位开始计数

for( ; i < length; ++i)

{

result = result * 10 + s[i] - 48; //48是字符0的ASCII码

}

if(s[0] == '-') return -1 * result;

else return result;

} 2.int i=(j=4,k=8,l=16,m=32); printf(“%d”, i); 输出是多少?

最后一个表达式是(m=32)该表达式将m赋值为32,并且返回1,所以i应当等于1.

13.编写strcat函数(6分)

已知strcat函数的原型是char *strcat (char *strDest, const char *strSrc); 其中strDest 是目的字符串,strSrc 是源字符串。将两个char类型链接,d和s所指内存区域不可以重叠且d必须有足够的空间来容纳s的字符串。 (1)不调用C++/C 的字符串库函数,请编写函数 strcat 答: VC源码: char * __cdecl strcat (char * dst, const char * src) { char * cp = dst; while( cp ) cp++; / find end of dst */ while( *cp++ = src++ ) ; / Copy src to end of dst / return( dst ); / return dst */ } (2)strcat能把strSrc 的内容连接到strDest,为什么还要char * 类型的返回值? 答:方便赋值给其他变量

14.编写类String 的构造函数、析构函数和赋值函数(25 分) 已知类String 的原型为: class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &other); // 拷贝构造函数 ~ String(void); // 析构函数 String & operate =(const String &other); // 赋值函数 private: char *m_data; // 用于保存字符串 };

14.请编写String 的上述4 个函数。

标准答案: // String 的析构函数 String::~String(void) // 3 分 { delete [] m_data; // 由于m_data 是内部数据类型,也可以写成 delete m_data; }

// String 的普通构造函数 String::String(const char *str) // 6 分 { if(str==NULL) { m_data = new char[1]; // 若能加 NULL 判断则更好 *m_data = ‘\0’; } else { int length = strlen(str); m_data = new char[length+1]; // 若能加 NULL 判断则更好 strcpy(m_data, str); } }

// 拷贝构造函数 String::String(const String &other) // 3 分 { int length = strlen(other.m_data); m_data = new char[length+1]; // 若能加 NULL 判断则更好 strcpy(m_data, other.m_data); }

// 赋值函数 String & String::operate =(const String &other) // 13 分 { // (1) 检查自赋值 // 4 分 if(this == &other) return *this; // (2) 释放原有的内存资源 // 3 分 delete [] m_data; // (3)分派新的内存资源,并复制内容 // 3 分 int length = strlen(other.m_data); m_data = new char[length+1]; // 若能加 NULL 判断则更好 strcpy(m_data, other.m_data); // (4)返回本对象的引用 // 3 分 return *this; }

(笔试题游览者自行著写)

(本面试题为个人总结整理,适用于1.5w左右薪资以下的求职者,欢迎指点评论及补充)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值