1、C++和C的区别
思想:C++面向对象 C面向过程的结构化语言
语法:C++具有封装、继承、多态三大特性
C++增加更多类型安全的功能,如强制类型转换
C++支持范式编程(模板)
2、static关键字的作用
(1)全局静态变量
在全局变量前加上关键字static,全局变量就被定义成一个全局静态变量;
存储在静态存储区,在整个程序运行期间一直存在;
未初始化的全局静态变量会自动初始化为0;
作用域为声明static的文件,文件外不可访问。
(2)局部静态变量
存储在静态存储区;
未初始化的全局静态变量会自动初始化为0;
作用域为函数体内,函数结束,变量失效,但是并没有销毁,重新调用函数时,仍然可以调用static变量,值不变。
(3)静态函数
在函数返回类型前加上static,函数就定义为静态函数。函数的定义和声明默认是extern类型的,但静态函数只能在声明它的文件中使用(如果函数前加上static,就不会同其他文件中的同名函数冲突);
注意:不要在头文件中声明静态全局函数;
不要在cpp文件中声明非static的全局函数
(4)类的静态成员
静态成员可以实现多个对象之间的数据共享,还不会破坏封装特性,因此静态成员是类的静态成员,而非对象的静态成员。
(5)类的静态成员函数
类的静态的成员函数是属于类的,不属于某一的对象,因此引用格式:<类名>::<静态成员函数名>(参数列表)。在静态成员函数的实现中不能直接引用类中说明的非静态成员。
3、C++中四种 cast 转换
(1)const_cast。用于将const变量转为非const。
(2)static_cast。用于各种隐式转化。例如 非const 转 const , void* 转为指针,static_cast能用于向上转化。
(3)dynamic_cast。动态类型转换。只能用于含有虚函数的类,用于层次间的向上(子类向基类的转换)或向下转化(基类向子类的转换)。
(4)reinterpret_cast。
另外:C的强制类型转换,转换的不明确,不能进行错误检查,容易出错。
4、内存分区
栈区:就是由编译器在需要时分配,不需要的时候自动清除的变量的存储区,通常存储局部变量、函数参数、返回值等
堆区:容量远远大于栈。由程序员申请并负责释放。
堆栈的区别:
(1)管理方式不同。栈区由系统管理,堆区由程序员管理,malloc()和free()、new()和delete(),应该是成对存在的,否则将会产生内存泄漏
(2)空间大小不同:
(3)碎片问题:对于堆,频繁的new()、delete()势必造成内存空间的不连续,从而造成大量的碎片,降低系统效率。栈区则不会有这种问题。
(4)生长方向:堆区,由低地址向高地址生长;栈区,由高地址向低地址生长。
(5)分配方式:栈区,可以静态分配,也可以动态分配;堆区都是动态分配。
(6)分配效率:栈分配比堆分配的效率更高
mmap:映射区
内存映射就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修改就会直接反映到内核空间。内核空间对内存的修改也会直接反映到用户空间。
具体参考:Linux内存映射mmap原理分析
5、C++中struct和class的区别
(1)默认访问属性不同。struct默认是public,而class是private的。
(2)在继承关系中的默认访问属性不同。在继承关系中,struct默认是public的,而class是private的。默认的访问属性取决于子类,而非基类。例如:类B默认继承结构体A,它的访问属性就是私有的;结构体B默认继承类A,那么他的默认继承属性就是公有的。为避免访问属性不明确的情况,在继承时应当显式指定继承方式。
(3)class可用于模板。
6、计算机中运算优先级:算法运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符
7、C++中的深拷贝与浅拷贝
拷贝构造函数是一种特殊的构造函数,它的任务就是拷贝对象。
默认的拷贝构造函数。当程序中没有自定义的拷贝函数时,系统自动提供一个缺省的拷贝构造函数,这个函数的访问属性是公有的。其有两种拷贝方式:
(1)普通成员(内置类型,指针等)按位拷贝,执行浅拷贝;
(2)对象成员,执行其拷贝函数,具体行为由拷贝构造函数决定。
浅拷贝:简单的复制拷贝操作。将源对象的值拷贝到目标对象中去,本质上源对象与目标对象共用一份实体,只是变量名不同,地址还是相同的。拷贝构造函数默认的是浅拷贝,只要不涉及到堆内存,浅拷贝完全可以。
//浅拷贝
#include <iostream>
using namespace std;
class Student
{
private:
int* m_hight; //身高
int m_age;
public:
Student(int hight,int age)
{
m_hight = new int(hight);
m_age = age;
cout << "Student构造函数" << endl;
}
~Student()
{
if (m_hight != NULL)
{//堆区内存程序员开辟,由程序员释放
delete m_hight;
}
m_hight = NULL; //防止野指针的出现
cout << "Student 析构函数" << endl;
}
};
int main()
{
Student p1(180,23);
Student p2(p1);
return 0;
}
运行这个程序,在执行到第二次析构函数的时候,程序崩溃了,此时使用深拷贝就可以解决问题。
深拷贝:在堆区重新申请空间,进行拷贝操作。拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样两个指针就指定了不同的内存位置,并且里边的内容也是一样的。这样两个指针先后去调用析构函数,分别释放自己指向的位置,就不会产生重复释放同一块内存的错误了。
//自定义的拷贝构造函数,实现深拷贝,如果不自己写,系统就会使用缺省的拷贝构造,就可能会出现程序崩溃的情况
//自己实现拷贝构造
Student(const Student& p)
{
cout << "拷贝构造" << endl;
//编译器默认此种拷贝方式
m_age = p.m_age; // 拷贝成功
// m_hight = p.m_hight; // 这行就会产生问题,注释掉
//深拷贝操作
m_hight = new int( * p.m_hight); //重新申请空间
}
8、C++中指针和引用的区别
(1)指针有自己的空间,引用只是一个别名;
(2)对于sizeof操作,指针的大小是4(32位),引用则是被引用对象的大小;
(3)指针可以被初始化为NULL,引用必须是一个已有对象的引用;
(4)指针在使用中可以指向其他对象,但是引用只能是一个对象的引用,不能改变;
(5)指针可以有多级指针,而引用只有一级;
(6)如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露;
(7)引用定义时必须初始化;
以下为扩展:
【引用 reference】
引用更接近 const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就一直效忠于它。即 int& val = num; 实际上就是 int * const val = &num;
程序示例来自C++ Primer Plus 中文第六版P257。
rodents = bunnies; 试图改变rodents引用,依据理论知识,这必然是失败的,从程序运行结果来看rodents的地址依然是rats的地址,对rodents值的改变也反应到了rats上。
引用可以作为函数参数void
swap_int(
int
&a,
int
&b)
、引用类型的函数(返回对象的引用)、常引用(const int& val= num;保护原值不受更新)、多态(父类引用指向子类对象)
//返回对象的引用,效率更高
struct free_throws {};
free_throws& accumulate(free_throws& target, const free_throws& source)
{
return target;
}