一、基本语言
1、C++与C的区别
设计思想上
C++是面向对象的语言,C是面向程序的结构化编程语言。
语法上:
(1)C++具有封装、继承和多态三种特性。
封装:封装就是将该隐藏的数据私有化,该开放的数据公有化(private、public)。可以隐藏细节,实现代码模块化。
继承:实现代码的复用,可以在已有代码的基础上进行扩展。继承又可以分为公有继承(public)、保护继承(protected)和私有继承(private)。所谓继承方式即(对于父类中数据)能够提供给子类的最大访问权限,而实际访问权限一定是小于等于继承方式。
多态:即当类之间存在继承关联时,在调用成员函数时,会根据调用函数的对象类型来调用不同的函数。多态又分为静态多态和动态多态,静态多态是通过函数重载来实现的,在编译时就能确定调用哪一个函数;而动态多态是通过虚函数(virtual) 来实现的,是在程序运行过程中才能确定操作所针对的对象。
(2)C++与C相比,增加了许多多类型安全的功能,如强制类型转换。
(3)C++支持范式编程,比如类模板和函数模板(template)。
2、C++中static关键字的作用(1)静态局部变量:用于函数体的内部修饰变量,该变量的生命周期大于函数的生命周期。(在内存中全局变量跟静态变量都是存放在全局数据区(静态区),因此其生命周期是相同的。)(静态变量若无显示初始化,默认为0)。
(2)静态全局变量:定义在函数体外,用于修饰全局变量,表示该变量只在本文件中可见,即在其他文件中无法使用extern关键字访问该变量。
(3)静态函数:与静态全局变量一样,主要是起到文件隔离作用。
以上是C与C++共有的用法,除此之外,C++还有两种用于修饰类内成员的方法。
(4)(类内)静态数据成员:用于修饰类的数据成员,数据成员的生存周期大于类的对象,所有类的对象共享访问静态数据成员。
(5)(类内)静态成员函数:用于修饰类的成员函数,可以通过类名::函数名直接访问该函数。静态成员函数无法访问非静态成员函数和非静态数据成员。
注:静态数据成员在内存中只有一个拷贝,非静数据成员段则是在每个实例对象中拥有一个拷贝。而成员函数无论是否为静态,在内存中只会有一份拷贝,区别是通过类名来访问是通过实例名来访问。
3、C++四中强制类型转换在C中,强制类型转换是通过()来实现的,这中转换方式转化不够明确,不能进行错误检查,且不易排查。C++中提供了static_cast、 const_cast、 reinterpret_cast、dynamic_cast 四个关键字用于强制类型转换。
(1)static_cast: 基本数据类型之间转换(如int转化为double); 基类和派生类之间指针和引用的转换(如果向下转换能成功但是不安全);空指针(void)与目标类型之间指针的相互转换;非const转换为const。
(2)const_cast: 强制取消指针或引用的const或volatile限制。
(3)reinterpret_cast:处于无关类型之间的转换,它会产生一个新的值,这个值与原始参数有完全相同的比特位。
(4)dynamic_cast: 是在运行时进行的转换,基类指针(或引用)和派生类指针(或引用)之间的上下转换,必须要有虚函数;向下转换时要进行类型检查,通过判断执行到该语句的时候变量的运行类型和要转换的类型是否相同来判断是否能够进行向下转换,失败则返回NULL,比static_cast 安全。
4、C/C++ 引用和指针的区别(1)指针是一个变量,存储的是地址,指向内存的一个存储单元;而引用跟原来的变量本质上是相同的东西(在内存中占用同一个存储单元),只不过是原变量的别名。
(2)指针可以有多级,但是引用只能是一级(如int **p合法而int &&a 不合法);
(3)指针可以为空,但引用的值不能为NULL,并且引用在定义时必须初始化。
(4)指针的值在初始化后可以改变,而引用则不可以。
(5)sizeof(引用)得到的是所指变量或对象的大小,而sizeof(指针)得到的是指针本身的大小。
(6)作为参数传递时,二者有本质的不同:指针传参本质是值传递,被调函数的形参作为局部变量在栈中开辟内存以存放由主调函数放进来的实参值,从而形成实参的一个副本。而引用传递时,被调函数对形参的任何一个操作都会通过一个间接寻址的方式影响主调函数中的实参变量。如果想通过指针参数传递来改变主调函数中的相关变量,可以使用指针引用。
5、C++中四个智能指针(smart pointer)C++里面有四个智能指针:auto_ptr、unique_ptr、shared_ptr、weak_ptr。其中后三个是C++11支持,并且第一个已经被11弃用。
为什么要使用智能指针:
智能指针的作用是避免申请的空间在函数结束时忘记释放而造成内存泄漏。智能指针就是一个指针管理类,当超出了类的作用域时,类会自动调用析构函数释放资源。
(1)auto_ptr:采用所有权模式, 当
auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr p2;
p2 = p1; //auto_ptr不会报错.
此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!
(2)unique_ptr:实现了独占式拥有或严格拥有的概念,保证同一时间只能有一个智能指针指向该对象。采用所有权模式,
unique_ptr p3 (new string ("auto"));
unique_ptr p4;
p4 = p3;//此时会报错!!`
(3)shared_ptr: 实现共享式拥有概念。多个智能指针可以指向相同的概念,它使用计数机制来表示资源被几个指针所共享,当调用relaese()时,当前指针会释放资源所有权,计数减一,当计数为零,资源会被释放。
(4)weak_ptr: 是一种不控制对象生命周期的只能指针,它指向一个shared_ptr 管理的对象,只是提供一个访问手段,在构造或析构时不用引起shared_ptr 计数器的增加或减少,主要是解决shared_ptr相互引用时的死锁问题。它和shared_ptr之间可以相互转化。
6、数组与指针的区别(1)定义:数组是类型相同的数目固定的若干个变量的有限集合,它存放的是数据。指针存放的是变量的地址。
(2)访问方式:数组是通过下标直接访问数据;指针是间接访问数据,即通过地址提取数据。
(3)存储方式:数组通常存储在静态区或栈上;指针可以随时随地的指向任意类型的内存块。
注:数组在传参时会退化为指针。因为C语言只会一值拷贝的方式来传递参数,如果拷贝整个数组,效率会大大降低,而且可能会导致内存溢出,因此C语言将数组传参进行退化,将数组名看做常量指针,传递数组首元素的地址。
7、野指针概念野指针就是未初始化或者指向一个已删除对象的指针。
8、为什么(基类)析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数?将可能会被继承的父类的析构函数定义为虚函数,可以保证当我们在定义一个父类指针指向子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
C++默认的析构函数不是虚函数的原因是虚函数需要额外的虚函数表和虚表指针,占用额外内存,对于不会被继承的类,如果析构函数是虚函数,则会浪费内存。
9、函数指针(1)定义:函数指针是指向函数的指针变量。C在编译时,每一个函数都有一个入口地址,函数指针指向的地址就是该入口地址。
(2)用途: 调用函数或做函数的参数(如回调函数)。
10、析构函数(1)析构函数与构造函数对应,当对象结束其生命周期时,系统会自动执行析构函数。析构函数只有一个,不能重载。
(2)如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数,不进行任何操作。
(3)如果一个类中存在动态申请内存的操作,则最好显示构造析构函数,释放申请的内存避免内存泄漏。
(4)类析构顺序:派生类本身析构函数;对象成员析构函数;基类析构函数。
11、覆盖和重载(1)覆盖也称为重写,覆盖是存在类中,子类重写从基类继承过来的函数,其函数名、返回值、参数列表都必须和基类相同。当子类的对象调用成原函数时,如果成员函数被覆盖,调用子类中被覆盖的版本,否则 调用从基类继承过来的函数。如果子类覆盖的是基类的虚函数,则可以通过多态来实现。
(2)重载指允许在相同作用域下定义多个重名函数,这些函数的参数列表不同,编译器根据不同的形参表对同名函数的名称做修饰,然后这些 这些同名函数就成了不同的函数。
(3)重载和覆盖的区别:
- 重载要求函数名相同,但是参数列表必须不同;覆盖要求函数名、参数列表、返回值必须相同。
- 重载描述的是同一个类中不同成员函数之间的关系;覆盖描述的是子类和基类之间不同成员函数之间的关系。
(1)strlen 函数是计算字符串常度的函数,返回从开始到' '之间的字符个数。
(2)strcpy是字符串拷贝函数,传入两个字符数组指针来执行,原型是
char* strcpy(char* dest, const char* src)
从src逐字节拷贝到dest,直到遇到' '结束,如果strlen(src)>strlen(dest),则最后的' '无法复制,导致拷贝越界,造成缓冲区溢出漏洞。
13、++i 和 i++区别i++先返回i,在自增;++i,先自增再返回i。
14、如何写一个函数在main函数执行前运行全局变量优先初始化
int runBeforeMain() {
printf("before mainn");
return 0;
}
int beforeMain = runBeforeMain();
int main(int argc, const char **argv)
{
printf("main startn");
//do something
printf("main endn");
}
15、如何定义一个常量
(1)概念:在程序运行过程中,始终不发生改变的量称为常量。
(2)定义常量的方法
- 宏定义(#define)
#define MAX = 200
宏定义是在预处理阶段起作用;宏常量没有数据类型,只是进行文本替换,没有类型检查。
- const 限定符,用来修饰变量,表示该变量不能修改。
const int max = 20;
- 枚举类(enum)
enum是C++中一种派生数据类型,他是由用户定义的若干枚举常量的集合。
enum color{red, green, blue}
枚举常量代表该枚举类型的变量可能渠道得知,编译系统为每个枚举常量指定一个整数值,默认状态下,这个整数值就是枚举元素的序号(从0开始)。
16、const修饰成员函数的目的(1)成员函数后加const则称这个函数为常函数
void show() const{...}
常函数不可以修改成员的属性,但是成员属性声明时加关键字mutable,在常函数中依然可以修改。
class Person {
public:
Person():m_A(0),m_B(0) {}
void ShowPerson() const {
//this->mA = 100; //错误,不能修改成员变量
this->m_B = 100; //正确,m_B 在定义时使用关键字mutable
}
void Show() {
//mA = 10000;
}
public:
int m_A;
mutable int m_B; //可修改 可变的
};
(2)在声明对象前加入const称为常对象,常对象只能调用常函数。
int main() {
const Person person; //常量对象
cout << person.m_A << endl;
//person.mA = 100; //错误,常对象不能修改成员变量的值,但是可以访问
person.m_B = 100; //正确,常对象可以修改mutable修饰成员变量
//常对象访问成员函数
person.ShowPerson(); //常对象可以调用const的函数
//person.Show(); //错误,常对象不可以调用非const函数
system("pause");
return 0;
}
(3)注:如果在类中定义了两个重名的成员函数,区别就只有一个是const函数,一个不带,运行时并不会出现重定义问题,因为这相当于函数的重载,此时会根据声明的对象是否为常量对象来选择调用那个函数。
17、C++的隐式类型转换隐式类型转换是指C++自动将一种类型转换成另一种类型,是编译器的一种自主行为。隐式类型转换主要有三种情况
- 对于内置数据类型,低精度的数据类型可能会隐式转换成高精度的数据类型。
int i = 3; double j ; j = i //i会被隐式转换成double类型,然后才做加法运算。
在赋值过程中,编译器会先构建一个double类型的临时变量接收 i 的值,然后再将该临时变量的值赋给 j 。
- 对于类来说,可以从子类隐式转换成父类,保证类对象功能的正常。
class A{}; class B: public A{};//B是子类 void Fun(A& a); B b; Fun(b);//使用子类对象代替父类对象是可以的,也是因为隐式类型转换。 - 特殊情况:对于只有一个参数的构造函数的对象构造来说,函数调用可以直接使用该参数传入,编译器会自动调用其构造函数生成临时对象。
class Test
{
public:
Test(int i){};
};
Test t1 = 1;//正确,由于强制类型转换,1先被Test构造函数构造成Test对象,然后才被赋值给t1
Test t2(1);//正确
注:隐式类型转换是编译器自发的行为,所以安全是第一位的,但是也可能会产生意想不到的危险。比如特殊情况的代码中直接给对象赋值的操作,如果程序员本意是这样的,那么就是节省代码;如果是错误的写出了这行代码,但是程序却运行了,那么导致的后果是灾难性的。而C++的explicit关键字可以解决这个问题。explicit关键字的作用就是防止类构造函数的隐式自动转换,explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的,所以也就无效了。
class Test
{
public:
explicit Test(int i){};
};
Test t2 = 1;//编译报错
Test t2(2);//编译没问题
18、C++ extern 关键字
(1)概念:extern 关键字是一个声明操作,这个声明就是告诉编译器这个变量或函数的定义在其它文件或模块中,可以让编译器把‘寻找定义’这件事推迟到链接阶段,而不会在编译阶段报‘没有定义’的错误。
(2)extern "C" 关键字主要作用是为了能够实现C++代码中调用C语言代码。当加上extern "C"后,会指示编译器这部分代码按C语言进行编译。主要原因由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。
比如说你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL输出(Export)的函数,你需要用extern "C"来强制编译器用C语言编译器来生成函数名。
首先,new/delete是C++的关键字,而malloc/free是C语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数。



