编程语言C/C++(四)
编程语言C/C++(四)
1. 堆和栈的区别?
- 申请方式不同。
- 栈由系统自动分配。
- 堆由程序员手动分配。
- 申请大小限制不同。
- 栈顶和栈底是之前预设好的,大小固定,可以通过ulimit -a
查看,由ulimit -s修改。
- 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。
- 申请效率不同。
- 栈由系统分配,速度快,不会有碎片。
- 堆由程序员分配,速度慢,且会有碎片。
2. delete和delete[]区别?
- delete只会调用一次析构函数。
- delete[]会调用数组中每个元素的析构函数。
3. 面向对象三大特性?
- 封装性:数据和代码捆绑在一起,避免外界干扰和不确定性访问。
- 继承性:让某种类型对象获得另一个类型对象的属性和方法。
- 多态性:同一事物表现出不同事物的能力,即向不同对象发送同一消
息,不同的对象在接收时会产生不同的行为(重载实现编译时多态,
虚函数实现运行时多态)。
4. C++空类有哪些成员函数?
- 首先,空类大小为1字节。
- 默认函数有:
- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值运算符
5. 构造函数能否为虚函数,析构函数呢?
- 析构函数:
- 析构函数可以为虚函数,并且一般情况下基类析构函数要定义为
虚函数。
- 只有在基类析构函数定义为虚函数时,调用操作符delete销毁
指向对象的基类指针时,才能准确调用派生类的析构函数(从该级
向上按序调用虚函数),才能准确销毁数据。
- 析构函数可以是纯虚函数,含有纯虚函数的类是抽象类,此时不
能被实例化。但派生类中可以根据自身需求重新改写基类中的纯虚
函数。
- 构造函数:
- 构造函数不能定义为虚函数。在构造函数中可以调用虚函数,不
过此时调用的是正在构造的类中的虚函数,而不是子类的虚函数,
因为此时子类尚未构造好。
6. 构造函数调用顺序,析构函数呢?
- 调用所有虚基类的构造函数,顺序为从左到右,从最深到最浅
- 基类的构造函数:如果有多个基类,先调用纵向上最上层基类构造函
数,如果横向继承了多个类,调用顺序为派生表从左到右顺序。
- 如果该对象需要虚函数指针(vptr),则该指针会被设置从而指向对应
的虚函数表(vtbl)。
- 成员类对象的构造函数:如果类的变量中包含其他类(类的组合),
需要在调用本类构造函数前先调用成员类对象的构造函数,调用顺序遵
照在类中被声明的顺序。
- 派生类的构造函数。
- 析构函数与之相反。
7. 拷贝构造函数中深拷贝和浅拷贝区别?
- 深拷贝时,当被拷贝对象存在动态分配的存储空间时,需要先动态申
请一块存储空间,然后逐字节拷贝内容。
- 浅拷贝仅仅是拷贝指针字面值。
- 当使用浅拷贝时,如果原来的对象调用析构函数释放掉指针所指向的
数据,则会产生空悬指针。因为所指向的内存空间已经被释放了。
8. 拷贝构造函数和赋值运算符重载的区别?
- 拷贝构造函数是函数,赋值运算符是运算符重载。
- 拷贝构造函数会生成新的类对象,赋值运算符不能。
- 拷贝构造函数是直接构造一个新的类对象,所以在初始化对象前不需
要检查源对象和新建对象是否相同;赋值运算符需要上述操作并提供两
套不同的复制策略,
另外赋值运算符中如果原来的对象有内存分配则需要先把内存释放掉。
- 形参传递是调用拷贝构造函数(调用的被赋值对象的拷贝构造函数),
但并不是所有出现"="的地方都是使用赋值运算符,如下:
Student s;
Student s1 = s; // 调用拷贝构造函数
Student s2;
s2 = s; // 赋值运算符操作
**注:类中有指针变量时要重写析构函数、拷贝构造函数和赋值运算符**
9. 虚函数和纯虚函数区别?
- 虚函数是为了实现动态编联产生的,目的是通过基类类型的指针指向
不同对象时,自动调用相应的、和基类同名的函数(使用同一种调用形
式,既能调用派生类又能调用基类的同名函数)。
虚函数需要在基类中加上virtual修饰符修饰,因为virtual会被隐式
继承,所以子类中相同函数都是虚函数。当一个成员函数被声明为虚函
数之后,其派生类中同名函数自动成为虚函数,
在派生类中重新定义此函数时要求函数名、返回值类型、参数个数和类
型全部与基类函数相同。
- 纯虚函数只是相当于一个接口名,但含有纯虚函数的类不能够实例化。
10. 覆盖、重载和隐藏的区别?
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不
论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基
类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与
覆盖混淆)
11. 在main执行之前执行的代码可能是什么?
- 全局对象的构造函数。
12. 哪几种情况必须用到初始化成员列表?
- 初始化一个const成员。
- 初始化一个reference成员。
- 调用一个基类的构造函数,而该函数有一组参数。
- 调用一个数据成员对象的构造函数,而该函数有一组参数。
13. 什么是虚指针?
- 虚指针或虚函数指针是虚函数的实现细节。
- 虚指针指向虚表结构。
14. this指针(指针常量)是什么?
- this指针是类的指针,指向对象的首地址。
- this指针只能在成员函数中使用,在全局函数、静态成员函数中都不
能用this。
- this指针只有在成员函数中才有定义,且存储位置会因编译器不同有
不同存储位置。
15. 文件流编程
数据类型描述
ofstream该数据类型表示输出文件流,用于创建文件并向文件写入信息。
ifstream该数据类型表示输入文件流,用于从文件读取信息。
fstream 该数据类型通常表示文件流,且同时具有 ofstream 和
ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,
从文件读取信息。
要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件
<iostream> 和 <fstream>。
打开文件
在从文件读取信息或者向文件写入信息之前,必须先打开文件。
ofstream 和 fstream 对象都可以用来打开文件进行写操作,如果只
需要打开文件进行读操作,则使用 ifstream 对象。
下面是 open() 函数的标准语法,open() 函数是 fstream、
ifstream 和 ofstream 对象的一个成员。
void open(const char *filename, ios::openmode mode);
在这里,open() 成员函数的第一参数指定要打开的文件的名称和位置,
第二个参数定义文件被打开的模式。
模式标志描述
ios::app追加模式。所有写入都追加到文件末尾。
ios::ate文件打开后定位到文件末尾。
ios::in打开文件用于读龋
ios::out打开文件用于写入。
ios::trunc如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。
ios::binary 以二进制方式打开
关闭文件
当 C++ 程序终止时,它会自动关闭刷新所有流,释放所有分配的内存,
并关闭所有打开的文件。但程序员应该养成一个好习惯,在程序终止前
关闭所有打开的文件。
下面是 close() 函数的标准语法,close() 函数是 fstream、
ifstream 和 ofstream 对象的一个成员。
void close();
写入文件
在 C++ 编程中,我们使用流插入运算符( << )向文件写入信息,就
像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用
的是 ofstream 或 fstream 对象,而不是 cout 对象。
读取文件
在 C++ 编程中,我们使用流提取运算符( >> )从文件读取信息,
就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用
的是 ifstream 或 fstream 对象,而不是 cin 对象。
文件位置指针
istream 和 ostream 都提供了用于重新定位文件位置指针的成员函
数。下面是关于定位 "get" 文件位置指针的实例:
// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
16. 关于类的占用空间:
- 在类中,如果什么都没有,则类占用1个字节,一旦类中有其他的占用
空间成员,则这1个字节就不在计算之内,如一个类只有一个int则占
用4字节而不是5字节;
- 如果只有成员函数,则还是只占用1个字节,因为类函数不占用空间;
- 虚函数因为存在一个虚函数表,需要4个字节(只算一次),数据成员
对象如果为指针则为4字节,注意有字节对齐,如果为13字节,则进位到
16字节空间;虚继承涉及虚表(虚指针);
-sizeof的本质是得到某个类型的大小,确切的来说就是当创建这个类
型的一个对象(或变量)的时候,需要为它分配的空间的大校而类也可
以理解为类似于int、float这样的一种类型,当类中出现static成员
变量的时候,static成员变量是存储在静态区当中的,它是一个共享的
量,因此,在为这个类创建一个实例对象的时候,是无需再为static成
员变量分配空间的,所以,这个类的实例对象所需要分配的空间是要排
除static成员变量的,于是,当sizeof计算类的大小的时候会忽略
static成员变量的大小
17. 关于引用标准库:
- 当用#include“file.h”时,先搜索当前工作目录,如果没有,再去
搜索标准库,库没有再搜索资源库;
- 当用#include<file.h>时,编译器先从标准库开始搜索,如果没再
搜索资源库目录,若还未找到则搜索当前工作目录。
- 综上,通过<>,(库里)一步就找到了;通过“”,由于先从当前目录
中找(未找到),再到库中寻找(找到),比前者多了一步,所以花费
时间比前者多
18. C++运算符重载机制:
- 不能被重载的运算符:
:: , * . ? : sizeof
- 必须作为成员函数重载的运算符:
= [] () ->
19. auto会忽略顶层const,所以const int a;auto b = a;中b的类型为int
20. 关于C++ downcast
- dynamic_cast(动态抛出)将一个基类对象指针(或引用)cast到
继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针
来做相应处理,即会作一定的判断。
对指针进行dynamic_cast,失败返回null,成功返回正常cast后的
对象指针;对引用进行dynamic_cast,失败抛出一个异常,成功返回
正常cast后的对象引用。
dynamic_cast用于动态类型转换。只能用于含有虚函数的类,用于类层
次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法
的对于指针返回NULL,对于引用抛异常。
- reinterpret_cast(重新解释抛出)这个转换是最“不安全”的,
两个没有任何关系的类指针之间转换都可以用这个转换实现。
- static_cast(静态抛出)静态转换是最接近于C风格转换,很多时
候都需要程序员自身去判断转换是否安全。
- const_cast(常量抛出)这个转换好理解,可以将常量转成非常量。
-结论:
1)static_cast用于内置的数据类型和具有继承关系的指针或者
引用;
EG:char c = static_cast<char>(a);
2)dynamic_cast只能转换具有继承关系的指针或者引用,在转
换前回进行对象类型安全检查,即子类指针可以转换为父类指针
(从大到小,类型安全),
但父类指针未必能转换为子类指针(从小到大,类型不安全);
EG:Animal* ani = dynamic_cast<Animal*>(cat);
3)const_cast用于为指针或者引用添加或者去除const性
EG:const char* str = “1234567”;
//注意:不能是char* str = “1234567”
char *ptr = const_cast<char*>(str);ptr[2]=’9’;
4)reinterpret_cast为强制类型转换,可以转换无关的指针类
型,包括函数指针
EG:FUNC2 func2 = reinterpret_cast<FUNC2>(func1);
21. 运算符优先级
- ! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
- 即:单目运算符 >算术运算符 >移位 >比较 >按位 >逻辑
>三目运算符 >赋值运算符
22. C++中关于实参与形参结合的两种方式
- 正如其他的普通的变量一样,形参的类型决定了形参与实参的结合方
式。如果一个形参是引用类型,那么这个形参被绑定到实参上,成为实
参的别名;
如果形参不是一个引用类型,那么实参的值将被拷贝并赋予形参。