关键字
static :控制变量的存储方式和可见性
- 修饰局部变量 该变量存储区由栈区变为静态数据区,其生命周期延长到整个程序执行结束。该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值。其作用域仍然是一个局部作用域。
- 修饰全局变量 改变了其作用域的范围,由原来的整个工程可见(
extern )变为本源文件可见。 - 修饰函数 与修饰全局变类似,改变了函数的作用域。
- C++ 中的 static 修饰成员函数,表示该函数属于一个类而不是属于此类的任何特定对象,不接收
this 指针,因而只能访问类的 static 成员变量。修饰成员变量,表示该变量为类以及其所有的对象所有。它们在存储空间中都只存在一个副本,可以通过类和对象去调用。
const :常量限定符,告知编译器该变量是不可修改的
- 修饰一般常量及数组 可以用在类型说明符前或类型说明符后。
- 修饰指针变量 * 及引用变量 & 位于星号的左侧,指针指向的变量为常量;位于星号的右侧,指针本身是常量。
- 修饰参数 进行常量化,保护了原对象的属性。通常用于参数为指针或引用的情况;
- 修饰返回值 按照"修饰原则"进行修饰,起到相应的保护作用。
- 修饰成员变量 初始化只能在类构造函数的初始化表中进行
- 修饰成员函数 不能修改所在类的的任何变量,重载函数的一个方式。
- 只能调用常量函数,别的成员函数都不能调用。
extern : 外部变量/函数声明引用
- 在
C 语言中,修饰变量/函数,表示此变量/函数是在别处定义的,要在此处引用。 调用其它文件中的变量/函数,只需把该文件用#include包含进来即可,为啥要用 extern ?因为用 extern 会加速程序的编译过程,这样能节省时间。 - 在
C++ 中 extern 还有另外一种作用,用于指示 C 或者 C++ 函数的调用规范。 在 C++ 中调用C 库函数,extern “C” 声明要引用的函数。告诉链接器在链接的时候用 C 函数规范来链接。主要原因是 C++ 和 C 程序编译完成后在目标代码中命名规则不同,用此来解决名字匹配的问题。
volatile :易变的
- 易变性。在汇编层面反映出来就是两条语句,下一条语句不会直接使用上一条语句对应的
volatile 变量的寄存器内容,而是重新从内存中读取。 - “不可优化”特性。
volatile 告诉编译器不要对变量进行优化,甚至将变量直接消除,保证写在代码中的指令一定会被执行。 - ”顺序性”。能够保证
volatile 变量间的顺序性,编译器不会进行乱序优化。
sizeof :判断数据类型或表达式长度的运算符
- 一个空类的对象占1个字节,单一继承空类的派生对象的基类成分占0个字节,虚继承涉及到虚指针所以占一个指针大小;
- 数组的长度:若指定了数组长度,则不看元素个数,总字节数 = 数组长度 * sizeof(元素类型)若没有指定长度,则按实际元素个数类确定。Ps:若是字符数组,则应考虑末尾的空字符。
- 结构体对象的长度; 在默认情况下,为方便对结构体内元素的访问和管理,当结构体内元素长度小于处理器位数的时候,便以结构体内最长的数据元素的长度为对齐单位,即为其整数倍。若结构体内元素长度大于处理器位数则以处理器位数为单位对齐。
- 对函数使用
sizeof ,在编译阶段会被函数的返回值的类型代替; sizeof 后如果是类型名则必须加括号,如果是变量名可以不加括号,这是因为 sizeof 是运算符;
- 当使用结构类型或者变量时,
sizeof 返回实际的大小。当使用静态数组时返回数组的全部大小,sizeof 不能返回动态数组或者外部数组的尺寸。
基础语法常见问题
1. 堆和栈的区别
- 申请方式不同:栈上有系统自动分配和释放;堆上有程序员自己申请并指明大小;
- 栈是向低地址扩展的数据结构,大小很有限;堆是向高地址扩展,是不连续的内存区域,空间相对大且灵活;
- 栈由系统分配和释放速度快;堆由程序员控制,一般较慢,且容易产生碎片;
2. 数组和指针的区别
- 数组要么在全局数据区被创建,要么在栈上被创建;指针可以随时指向任意类型的内存块;
- 修改内容上的差别:
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
- 用运算符
sizeof 可以计算出数组的容量(字节数)。sizeof(p) , p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C/C++ 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针.
3. 全局变量和局部变量的区别
- 生命周期不同:全局变量随主程序创建而创建(在main函数之前),随主程序销毁而销毁;
局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在; - 作用域不同:全局变量程序的各个部分都可以用到;局部变量只能在局部使用;
- 内存位置不同:全局变量分配在全局数据段并且在程序开始运行的时候被加载,局部变量则分配在堆栈里面。操作系统和编译器通过内存分配的位置来知道的
4. 空指针和悬垂指针的区别
空指针是指被赋值为 NULL 的指针;delete 指向动态分配对象的指针将会产生悬垂指针。
- 空指针可以被多次delete,而悬垂指针再次删除时程序会变得非常不稳定;
- 使用空指针和悬垂指针都是非法的,而且有可能造成程序崩溃,如果指针是空指针,尽管同样是崩溃,但和悬垂指针相比是一种可预料的崩溃。
5. sizeof 与 strlen 的区别
sizeof 的返回值类型为 size_t(unsigned int) ;sizeof 是运算符,而 strlen 是函数;sizeof 可以用类型做参数,其参数可以是任意类型的或者是变量、函数,而 strlen 只能用char* 做参数,且必须是以 ’\0’ 结尾;- 数组作
sizeof 的参数时不会退化为指针,而传递给 strlen 是就退化为指针; sizeof 编译时的常量,而 strlen 要到运行时才会计算出来,且是字符串中字符的个数而不是内存大小;
6. const 和 #define 的区别
- const和#define都可以定义常量,但是const用途更广。
- const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
- 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
7. 文件包含用中括号和引号的区别
前者是从系统包含目录中搜索该文件,找不到会在源文件目录搜索; 后者是从源文件目录中搜索该文件,找不到会在系统包含目录中搜索。 Linux 系统目录 /usr/include /usr/local/include
8. 指针(*)与引用(&)的区别
- 指针是个存储地址的变量。引用是原变量的别名;
- 指针可以为空,引用不能为空,定义时必须初始化;
- 指针初始化后可以改变,引用初始化后就不会再改变(不能再引用别的变量);
sizeof 指针是指针本身大小,sizeof 引用是所指向变量的大小;- 指针和引用的自增运算意义不一样;
9. 重载(overload)和重写(override)的区别
派生类重新定义基类虚函数的做法叫做重写;重载就在允许在相同作用域中存在多个同名的函数,这些函数的参数表不同。编译器根据函数不同的形参表对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。 重载的确定是在编译时确定,是静态的;虚函数则是在运行时动态确定。
10. c 与 c++ 各自特点
C 面向过程,一种结构化语言,重点在于算法和数据结构,考虑的是如何通过一个过程,对输入(或环境条件)进行处理得到输出(或实现过程(事务)控制)。 c++ 面向对象,考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。
11. 什么情况下需要在初始化列表进行初始化?
四种情况:
- 当初始化一个
reference member 时;(声明时必须初始化) - 当初始化一个
const member 时;(声明时必须初始化) - 当调用一个
base class 的 constructor ,而它拥有一组参数时; (无默认构造函数) - 当调用一个
member class 的 constructor ,而它拥有一组参数时; (无默认构造函数)
error: call to implicitly-deleted default constructor of 'Derived'
12. derived classes 是否可重新定义继承而来的 private virtual 函数?
class Base {
public:
virtual ~Base() = default;
private:
virtual void f(){} // private virtual function
};
class Derived :public Base {
private:
virtual void f() override {} // override the function f() of base class
};
13. 多态类中的虚函数表是 Compile-Time ,还是 Run-Time 时建立的?
虚函数表是在编译期就建立了,各个虚函数这时被组织成了一个虚函数的入口地址的数组。而对象的隐藏成员虚函数表指针是在运行期,也就是构造函数被调用时进行初始化的,这是实现多态的关键。
14. 为什么构造函数不能为虚函数?
- 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。
- 虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。
15. 为什么基类析构函数是虚函数?
编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏。
16. 面向对象技术的基本概念是什么,三个基本特征是什么?
基本概念:类、对象、继承; 基本特征:封装、继承、多态。 封装:将低层次的元素组合起来形成新的、更高实体的技术。 继承:广义的继承有三种实现形式:实现继承、可视继承、接口继承。 多态:允许将子类类型的指针赋值给父类类型的指针。
17. C++ 中有 malloc/free ,为什么还有 new/delete ?
malloc/free 是C/C++标准库函数,new/delete 是C++运算符。对于内置类型数据而言,二者没有多大区别。 malloc 申请内存的时候要制定分配内存的字节数,而且不会做初始化;new 申请的时候有默认的初始化,同时可以指定初始化; 对于类类型的对象而言,用 malloc/free 无法满足要求的。对象在创建的时候要自动执行构造函数,消亡之前要调用析构函数。由于 malloc/free 是库函数而不是运算符,不在编译器控制之内,不能把执行构造函数和析构函数的任务强加给它,因此,C++ 还需要 new/delete 。
18. 头文件中的 ifndef define endif 的作用
防止该头文件被重复引用,这是C++预编译头文件保护符,保证即使文件被多次包含,头文件也只定义一次。
|