转载

那些不能遗忘的知识点回顾——C/C++系列

有那么一些零碎的小知识点,偶尔很迷惑,偶尔被忽略,偶然却发现它们很重要,这段时间正好在温习这些,就整理在这里,一起学习一起提高!后面还会继续补充。

——前言

1.面向对象的特性

封装、继承、多态。

封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。(优点:可以隐藏实现细节,使得代码模块化)

继承:可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。(优点:可以扩展已存在的代码模块(类))

多态:一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。虽然针对不同对象的具体操作不同,但通过一个公共的类,这些操作可以通过相同的方式被调用。

多态实现的两种方式:将子类对象的指针赋给父类类型的指针或将一个基类的引用指向它的派生类实例。(重要:虚函数 + 指针或引用)

构造函数、复制构造函数、析构函数、赋值运算符不能被继承。

2.堆和栈

从内存角度来说:栈区(stack)由编译器自动分配释放,存放函数的参数值,局部变变量的值等,其操作方式类似于数据结构中的栈,可静态亦可动态分配。

堆区(heap)一般由程序员分配释放,若程序员不释放,可能造成内存泄漏,程序结束时可能由OS回收。动态分配,分配方式类似于链表。

从数据结构角度来说:堆可以被看成是一棵树,如:堆排序。

而栈是一种先进后出的数据结构。

3.malloc和new

1.malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。但它们都可用于申请动态内存和释放内存。

2.对于非内部数据类型的对象而言,用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free,因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,和一个能完成清理与释放内存工作的运算符delete。

3.new可以认为是malloc加构造函数的执行。new出来的指针是直接带类型信息的。而malloc返回的都是void*指针。new delete在实现上其实调用了malloc,free函数。

4.new 建立的是一个对象;alloc分配的是一块内存。

4.虚函数实现机制,虚继承在sizeof中有没有影响,构造函数能否为虚函数,与纯虚函数

虚函数表:类的虚函数表是一块连续的内存,每个 内存单元 中记录一个JMP指令的地址。

编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚函数占据虚函数表中的一块。如果类中有N个虚函数,那么其虚函数表将有N*4字节的大小。

在有虚函数的类的实例中分配了指向这个表的 指针 的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

编译器 应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是 多重继承 的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中 函数指针 ,并调用相应的函数。

->有虚函数或虚继承的类实例化后的对象大小至少为4字节(还要加上其他非静态数据成员,还要考虑对齐问题);没有虚函数和虚继承的类实例化后的对象大小至少为1字节(没有非静态数据成员的情况下也要有1个字节来记录它的地址)。

特别的:构造函数不能为虚函数。

5.面向对象的多态、多态的实现机制,多态的例子

见知识点4

6.对一个类求sizeof需要考虑的内容

见知识点4

7.重载和重写(覆盖)

方法的重写Overriding和重载Overloading是多态性的不同表现。

重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。

如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了,而且如果子类的方法名和参数类型和个数都和父类相同,那么子类的返回值类型必须和父类的相同;如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloading的方法是可以改变返回值的类型。也就是说,重载的返回值类型可以相同也可以不同。

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

引用是除指针外另一个实现多态的方式。这意味着,一个基类的引用可以指向它的派生类实例。例:

Class A; Class B : Class A{…};

B b; A& ref = b;

9.计算机加载程序包括哪几个区?

一个由C/C++编译的程序占用的内存分为以下几个部分:

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

(2)堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。动态分配。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 

(3)全局区(静态区):—程序结束后由系统释放,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域;未初始化的全局变量和静态变量在相邻的另一块区域(BSS,Block Started by Symbol),在程序执行之前BSS段会自动清0。 

(4)文字常量区:—程序结束后由系统释放,常量字符串就是放在这里的。  

(5)程序代码区:—存放函数体的二进制代码。

10.派生类中构造函数与析构函数,调用顺序

构造函数的调用顺序总是如下:

1.基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。

2.成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。

3.派生类构造函数。

析构函数正好和构造函数相反。

11.extern的作用

extern "C"实现C++与C及其它语言的混合编程,是用在C和C++之间的桥梁。之所以需要这个桥梁是因为C编译器编译函数时不带函数的类型信息,只包含函数符号名字;而C++编译器为了实现函数重载,编译时会带上函数的类型信息,如他把上面的a函数可能编译成_a_float这样的符号为了实现重载。

extern "C"的惯用法:

在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"{

#include "cExample.h"

}

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

12.析构函数、构造函数能不能被继承

见知识点1

13.C++为什么用模板类,为什么用泛型

通过泛型可以定义类型安全的数据结构(类型安全),而无须使用实际的数据类型(可扩展)。这能够显著提高性能并得到更高质量的代码(高性能),因为您可以重用数据处理算法,而无须复制类型特定的代码(可重用)。

14.结构体内存对齐,与什么有关(CPU)

在系统默认的对齐方式下:每个成员相对于这个结构体变量地址的偏移量正好是该成员类型所占字节的整数倍,且最终占用字节数为成员类型中最大占用字节数的整数倍。

详细分析见博客 http://www.cnblogs.com/webary/p/4721017.html

为什么要对齐?当CPU访问正确对齐的数据时,它的运行效率最高,当数据大小的数据模数的内存地址是0时,数据是对齐的。例如:WORD值应该是总是从被2除尽的地址开始,而DWORD值应该总是从被4除尽的地址开始,数据对齐不是内存结构的一部分,而是CPU结构的一部分。当CPU试图读取的数值没有正确的对齐时,CPU可以执行两种操作之一:产生一个异常条件;执行多次对齐的内存访问,以便读取完整的未对齐数据,若多次执行内存访问,应用程序的运行速度就会慢。

15.指针和引用

1.指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。

2.指针可以有多级,但是引用只能是一级;

3.指针的值可以为空,也可能指向一个不确定的内存空间,但是引用的值不能为空,并且引用在定义的时候必须初始化为特定对象;(因此引用更安全)

4.指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变引用对象了;

5.sizeof引用得到的是所指向的变量(对象)的大小,而sizeof指针得到的是指针本身的大小;

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

16.static关键字作用

在C语言中,关键字static有三个明显的作用:

1)在函数体内,一个被声明为静态的变量在这一函数被调用过程中维持上一次的值不变,即只初始化一次(该变量存放在静态变量区)。

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

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

除此之外,C++中还有新用法:

4)在类中的static成员变量意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见;

5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量(当然,可以通过传递一个对象来访问其成员)。

17.虚表,基类的虚表是什么样的,派生类虚表

(1)单继承情况

(2)多重继承(无虚函数覆盖)

(3)多重继承(有虚函数覆盖)

详细的内容参考博文: 关于C++虚函数表的那些事儿

18.volatile

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。

C++系列的暂时整理到这里吧,如果读者发现还有哪些这方面的经典常考知识点也请指出,待续~

转载请注明出处,谢谢!

正文到此结束
Loading...