C++是什么

C++是在C语言基础上开发的一种面向对象的编程语言。并支持多种编程泛式:面向对象,泛型编程和过程化编程。常用于系统开发和引擎开发等领域。支持类、封装、重载等特性。

C++中的struct和class有什么区别?

  1. 默认继承权限:class默认为私有继承;struct默认为共有继承
  2. 成员默认访问权限:class的成员默认是private权限;Struct默认是public权限

除以上两点外,class和struct基本就是一个东西

  1. struct A{};
  2. class B : A{}; // private 继承
  3. struct C : B{}; // public 继承

C和C++有什么区别?

  1. 从机制上:C是面向过程的(但C也可以编写面向对象的程序),重点在于算法和数据结构,对输入进行运算得到输出;C++是面向对象的,提供了类(C++编写面向对象的程序比C容易),考虑的是如何构造一个对象模型。
  2. 从适用的方向:C适合要求代码体积小的,效率高的场合,如嵌入式;C++适合更上层的,复杂的系统。 llinux核心大部分是C写的,因为它是系统软件,效率要求极高。
  3. C++ 对 C 的“增强”,表现在以下几个方面:类型检查更为严格。增加了面向对象的机制、泛型编程的机制(Template)、异常处理、运算符重载、标准模板库(STL)、命名空间(避免全局命名冲突)。
  4. 应用领域:C 语言主要用于嵌入式领域,驱动开发等与硬件直接打交道的领域,C++ 可以用于应用层开发,用户界面开发等与操作系统打交道的领域。

    何为面向对象

    面向对象是一种对于现实世界的理解和抽象的思想和方法。通过将需求要素转换为对象进行问题处理的一种思想。

引用和指针的区别?

指针

指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作,即可以改变所指向的对象。
Note:程序中使用指针,程序的可读性差。

引用

引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。
引用必须初始化。
引用初始化以后不能被该改变
不存在指向空值的引用,但是存在指向空值的指针

常引用

既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。

常引用声明方式:const 类型标识符 &引用名=目标变量名;

指针数组和数组指针

int p[4]是定义了长度为4的数组,数组中每个元素都指向整型变量,即指针数组,和int (p[4])完全相同
int (p)[4]定义了数组指针,指向长度是4的整型数组的一个指针
int *
p定义类二维指针
int p[4]定义类长度为4的数组

野指针和悬空指针

悬空指针

若指针指向一块内存空间,当这块内存空间被释放后,该指针依然指向这块内存空间,此时,称该指针为“悬空指针”。

  1. void *p = malloc(size);
  2. free(p);
  3. // 此时,p 指向的内存空间已释放, p 就是悬空指针。

野指针

“野指针”是指不确定其指向的指针,未初始化的指针为“野指针”。

  1. void *p;
  2. // 此时 p 是“野指针”。

数组名作为参数

数组名作为参数时,实际上传递的是地址。
而其他类型如int,作为参数时,则是传递的一份拷贝

重复多次 fclose 一个打开过一次的 FILE *fp 指针会有什么结果

导致文件描述符结构中指针指向的内存被重复释放,进而导致一些不可预期的异常。

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

格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }
好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
注意事项:

  • 不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了”无所指”的引用,程序会进入未知状态。
  • 不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。(可以参考我在智能指针那一节中,所采用shared_ptr进行的内存管理)
  • 可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
  • 流操作符重载返回值申明为“引用”的作用. 流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << “hello” << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。
  • 在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

重载(overload)、重写(override,覆盖)、重定义的区别

重载 是指同一可访问区内被声明几个具有不同参数列(参数的类型、个数、顺序)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。

  1. class A
  2. {
  3. public:
  4. void fun(int tmp);
  5. void fun(float tmp); // 重载 参数类型不同(相对于上一个函数)
  6. void fun(int tmp, float tmp1); // 重载 参数个数不同(相对于上一个函数)
  7. void fun(float tmp, int tmp1); // 重载 参数顺序不同(相对于上一个函数)
  8. int fun(int tmp); // error: 'int A::fun(int)' cannot be overloaded 错误:注意重载不关心函数返回类型
  9. };

重写/覆盖 重写(覆盖):是指派生类中存在重新定义的函数。函数名、参数列表、返回值类型都必须同基类中被重写的函数一致,只有函数体不同。派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有 virtual 修饰。

  1. #include <iostream>
  2. using namespace std;
  3. class Base
  4. {
  5. public:
  6. virtual void fun(int tmp) { cout << "Base::fun(int tmp) : " << tmp << endl; }
  7. };
  8. class Derived : public Base
  9. {
  10. public:
  11. virtual void fun(int tmp) { cout << "Derived::fun(int tmp) : " << tmp << endl; } // 重写基类中的 fun 函数
  12. };
  13. int main()
  14. {
  15. Base *p = new Derived();
  16. p->fun(3); // Derived::fun(int) : 3
  17. return 0;
  18. }

重定义/隐藏 是指派生类的函数屏蔽了与其同名的基类函数,主要只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。

  1. #include <iostream>
  2. using namespace std;
  3. class Base
  4. {
  5. public:
  6. void fun(int tmp, float tmp1) { cout << "Base::fun(int tmp, float tmp1)" << endl; }
  7. };
  8. class Derive : public Base
  9. {
  10. public:
  11. void fun(int tmp) { cout << "Derive::fun(int tmp)" << endl; } // 隐藏基类中的同名函数
  12. };
  13. int main()
  14. {
  15. Derive ex;
  16. ex.fun(1); // Derive::fun(int tmp)
  17. ex.fun(1, 0.01); // error: candidate expects 1 argument, 2 provided
  18. return 0;
  19. }

说明:上述代码中 ex.fun(1, 0.01); 出现错误,说明派生类中将基类的同名函数隐藏了。若是想调用基类中的同名函数,可以加上类型名指明 ex.Base::fun(1, 0.01);,这样就可以调用基类中的同名函数。

重写和重载的区别

范围区别:对于类中函数的重载或者重写而言,重载发生在同一个类的内部,重写发生在不同的类之间(子类和父类之间)。
参数区别:重载的函数需要与原函数有相同的函数名、不同的参数列表,不关注函数的返回值类型;重写的函数的函数名、参数列表和返回值类型都需要和原函数相同,父类中被重写的函数需要有 virtual 修饰。
virtual 关键字:重写的函数基类中必须有 virtual关键字的修饰,重载的函数可以有 virtual 关键字的修饰也可以没有。

隐藏和重写,重载的区别

范围区别:隐藏与重载范围不同,隐藏发生在不同类中。
参数区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定相同;当参数不同时,无论基类中的函数是否被 virtual 修饰,基类函数都是被隐藏,而不是重写。

C++函数中值的传递方式有几种?

值传递,指针传递和引用传递

静态全局变量的作用域?

静态全局变量限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它

C++中Virtual与Inline的含义分别是什么?

在基类成员函数的声明前加上virtual关键字,意味着将该成员函数声明为虚函数。
inline与函数的定义体放在一起,使该函数称为内联。inline是一种用于实现的关键字,而不是用于声明的关键字。

虚函数的特点;

如果希望派生类能够重新定义基类的方法,则在基类中将该方法定义为虚方法,这样可以启用动态联编。

纯虚函数

纯虚函数在类中声明时,加上 =0;
含有纯虚函数的类称为抽象类(只要含有纯虚函数这个类就是抽象类),类中只有接口,没有具体的实现方法;
继承纯虚函数的派生类,如果没有完全实现基类纯虚函数,依然是抽象类,不能实例化对象。
说明:

抽象类对象不能作为函数的参数,不能创建对象,不能作为函数返回类型;
可以声明抽象类指针,可以声明抽象类的引用;
子类必须继承父类的纯虚函数,并全部实现后,才能创建子类的对象。

内联函数的特点;

使用内联函数的目的是为了提高函数的运行效率。
内联函数体的代码不能过长,因为内联函数省去调用函数的时间是以代码膨胀为代价的。
内联函数不能包含循环语句,因为执行循环语句要比调用函数的开销大

assert()的作用?

断言assert是仅在debug版本起作用的用于检查“不应该“发生的情况。
程序员可以把assert看成一个在任何系统状态下都可以安全使用的无害测试手段
其作用是如果条件满足,则终止程序的执行,可以通过定义NDEBUG来关闭assert,但是需要写在源代码的开头,include 之前
c的头文件是 C++的头文件是

  1. #define NDEBUG // 加上这行,则 assert 不可用
  2. #include <assert.h>
  3. assert( p != NULL ); // assert 不可用


const与#define(宏)相比较,const有什么优点?

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

typedef与#define的区别

原理

define 作为预处理指令,在编译预处理时进行替换操作,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。typedef 是关键字,在编译时处理,有类型检查功能,用来给一个已经存在的类型一个别名,但不能在一个函数定义里面使用 typedef 。

功能

typedef 用来定义类型的别名,方便使用。#define 不仅可以为类型取别名,还可以定义常量、变量、编译开关等。

作用域

define 没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而 typedef 有自己的作用域。

指针的操作

typedef 和 #define 在处理指针时不完全一样

  1. #include <iostream>
  2. #define INTPTR1 int *
  3. typedef int * INTPTR2;
  4. using namespace std;
  5. int main()
  6. {
  7. INTPTR1 p1, p2; // p1: int *; p2: int
  8. INTPTR2 p3, p4; // p3: int *; p4: int *
  9. int var = 1;
  10. const INTPTR1 p5 = &var; // 相当于 const int * p5; 常量指针,即不可以通过 p5 去修改 p5 指向的内容,但是 p5 可以指向其他内容。
  11. const INTPTR2 p6 = &var; // 相当于 int * const p6; 指针常量,不可使 p6 再指向其他内容。
  12. return 0;
  13. }


noexcept修饰符

noexcept表示其修饰的函数不会抛出异常。与throw()动态异常声明不同的是,在C++11中如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行,这比基于异常机制的throw()在效率上会高一些。这是因为异常机制会带来一些额外的开销,比如函数抛出异常,会导致函数栈被依次地展开,并依帧调用在本帧中已构造的自动变量的析构函数,
语法上,有两种使用noexcept的形式:

一种是简单地在函数声明后加上noexcept关键字

  1. void excpt_func() noexcept

另一种是可以接受一个常量表达式作为参数

  1. void excpt_func() noexcept (常量表达式)

不使用”if”,”?:”,”switch”或是其它判断条件,获取两个数中较大的数

  1. ((a+b)+abs(a-b))/2

C++如何打印出当前源文件的文件名及源文件的当前行号?

  1. cout << __FILE__ ;
  2. cout<<__LINE__ ;

FILELINE是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的

malloc/free和new/delete

malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符 (注意:不是库函数。)。它们都可用于申请动态内存和释放内存。
如果在申请动态内存时找不到足够大的内存块,malloc 和 new 将返回 NULL 指针,宣告内存申请失败。
**
对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于 malloc/free。
因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符 new,以及一个能完成清理与释放内存工作的运算符 delete。
在new的过程中,编译器不会检查析构函数是否可以访问,因此可以通过将析构函数设置为private来使对象不能够直接创建,而是只能通过new来进行创建

C++不是类型安全的,因为不同类型的指针之间可以强制转换

如果通过C++判断操作系统是16位还是32位?

定义一个指针p,打印出sizeof(p),如果节后是4,则表示该操作系统是32位,打印结果是2,表示是16位。

sizeof和strlen

sizeof是C++中的运算符,而strlen是头文件中的函数

  1. size_t strlen(const char *str)
  2. {
  3. size_t length = 0;
  4. while(*str++)
  5. ++length;
  6. return length;
  7. }

如果arr是作为参数进行传递的,那么sizeof就是判断的指针的大小,64bit系统中,指针的大小是8

  1. #include <iostream>
  2. #include <cstring>
  3. using namespace std;
  4. int main()
  5. {
  6. char arr[10] = "hello";
  7. cout << strlen(arr) << endl; // 5
  8. cout << sizeof(arr) << endl; // 10
  9. return 0;
  10. }
  1. #include <iostream>
  2. #include <cstring>
  3. using namespace std;
  4. void size_of(char arr[])
  5. {
  6. cout << sizeof(arr) << endl; // warning: 'sizeof' on array function parameter 'arr' will return size of 'char*' .
  7. cout << strlen(arr) << endl;
  8. }
  9. int main()
  10. {
  11. char arr[20] = "hello";
  12. size_of(arr);
  13. return 0;
  14. }
  15. /*
  16. 输出结果:
  17. 8
  18. 5
  19. */


C++头文件后缀名:.h .hpp .hxx

hpp,其实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要include该hpp文件即可,无需再将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,采用hpp将大幅度减少调用 project中的cpp文件数与编译次数,也不用再发布烦人的lib与dll,因此非常适合用来编写公用的开源库。
编写hpp需要注意的是:

  • .h里面可以有using namespace std,而.hpp里则无。
  • 不可包含全局对象和全局函数。 由于hpp本质上是作为.h被调用者include,所以当hpp文件中存在全局对象或者全局函数,而该hpp被多个调用者include时,将在链接时导致符号重定义错误。要避免这种情况,需要去除全局对象,将全局函数封装为类的静态方法。
  • 类之间不可循环调用。
  • 不可使用静态成员。 静态成员的使用限制在于如果类含有静态成员,则在hpp中必需加入静态成员初始化代码,当该hpp被多个文档include时,将产生符号重定义错误。

C,C++文件常用后缀

C 中头文件 .h 源文件 .c
C++头文件 .h .hpp .hxx 源文件 .cpp .cc .cxx .C .c++

不同系统或编译器的定义

Unix: .C .cc .cxx .c
GNU C++: .C .cc .cxx .cpp .c++
Digital Mars: .cpp .cxx
Borland: .c++ .cpp
Watcom: .cpp
VC ++: .cpp .cxx .cc
Metrowerks CodeWarrior: .cpp .cp .cc .cxx .c++

编译器偏好

VS默认采用.cpp .h作为后缀
GCC默认采用.cc .h作为后缀

GCC 不同后缀含义

.c C语言源代码文件
.a 目标文件构成的档案库文件
.C .cc .cxx C++源代码文件
.h 程序所包含的头文件
.i 已经预处理过的C源代码文件
.ii 已经预处理过的C++源代码文件
.m Objective-C源代码文件
.o 编译后的目标文件
.s 汇编语言源代码文件
.S 经过预编译的汇编语言源代码文件