1、基本语言
static关键字的作用
-
定义全局静态变量
- 内存位置:静态存储区
- 生命周期:整个程序运行期间都存在,即程序运行开始到程序结束
- 初始化:未经初始化的自动为0
- 作用域:文件作用域(只在声明它的文件内可见)
-
定义局部静态变量
- 内存位置:静态存储区
- 生命周期:整个程序运行期间都存在,即程序运行开始到程序结束
- 初始化:未经初始化的自动为0
- 作用域:局部作用域(定义它的函数或者语句块结束时作用域结束,但变量还在内存中,直到该函数再次被调用,值不变)
-
定义静态函数
- 作用域:文件作用域(只在声明它的文件内可见,只可在本cpp使用,不会同其他cpp中的同名函数起冲突)
- 注意:不要在头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果要多个cpp中复用该函数,就把它的声明提到头文件,否则cpp内部声明需加static修饰。
-
定义类的静态成员
静态成员是类的所有对象共享的成员。
-
定义类的静态函数
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。如果静态成员函数要引用非静态成员,可以通过对象来引用。(<类名>::<静态成员函数名>(<参数表>))
const的用法
- const修饰的变量,相较于宏定义,可以进行类型检查,节省内存,提高了效率
- const修饰的函数参数,在函数中,参数值不可改变
- const修饰成员函数,使得成员函数不能修改任何类型的成员变量(mutable 修饰的变量除外,函数参数也除外),也不能调用非 const 成员函数,因为非 const 成员函数可能会修改成员变量
const和static在类中的注意事项
static静态成员变量
- 静态成员变量在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化时不要出现static关键字和public之类的访问规则。
- 静态成员变量相当于类域中的全局变量,被类的所有对象共享,包括派生类对象
- 静态成员变量可以作为成员函数的可选参数,普通变量不可以
- 静态成员变量可以是所属类的类型,而普通数据成员只能声明成类的指针或引用
static静态成员函数
- 静态成员函数不能调用非静态成员变量和函数,因为静态成员函数无this指针。静态函数可作为类作用域的全局函数
- 静态成员函数不能声明成虚函数、const、volatile
const成员变量
- const成员变量只能在类内声明和定义,在构造函数初始化列表中初始化
- const成员变量只在某个对象的生命周期内是常量,对于整个类而言是可变的,因为类可以创建多个对象,不同对象的const成员变量值是不同的,所以不能在类的声明中初始化const成员变量,因为类对象还没创建,编译器不知道它的值。
const成员函数
- 不能修改成员变量的值,除非有mutable修饰
- 只能访问成员变量
- 不能调用非常量成员函数,以防修改成员变量的值
const static
- 如果要想成员变量在整个类内都是恒定的常量,应该用类的枚举常量或者static const
- 在类中进行声明,在类外进行初始化
面向对象和面向过程的区别
- 面向过程:以过程为中心的编程思想,将解决一个问题的基本步骤列出来,按照执行顺序逐一完成每一个步骤。
- 面向对象:以对象为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。
面向对象的三大特性
-
封装:将具体的数据和实现过程封装为类,只能通过接口访问,降低耦合性。
耦合性:是指一程序中,模块及模块之间信息或参数依赖的程度。
-
继承:子类继承父类的行为和属性,子类拥有父类中非私有的变量和函数。子类可以重写父类中的方法,增强了耦合性。被final修饰的类不能被继承,被final修饰的成员不能被重写或修改。
-
多态:不同子类对同一个消息,作出不同的反应。基类的指针指向子类的对象,使得基类指针作出不同的应答。
多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定函数地址;动态多态是用虚函数机制实现的,在运行期间动态绑定。
多态的作用- 隐藏实现细节,使代码能够模块化
- 实现代码重用,利用接口和抽象类可以设计更通用的功能
如何实现多态
C++的多态分为两类,一个是编译期多态,一个是运行期多态。
编译期多态主要是通过模板类来实现的。
运行期的多态主要是通过虚函数结合动态绑定实现的
虚函数如何实现多态?
子类重写父类虚函数,虚函数表中,该函数的地址直接替换父类虚函数在虚表中的位置。访问虚函数表,首先取出vptr的值,这个值就是vtbl的地址,再根据这个值来到vtbl,然后就可根据调用的虚函数取出slot存放的函数地址,最后调用这个函数。对象模型的头部一般存放虚函数表的指针,通过该机制实现多态。
只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。
虚函数
在有虚函数的类中,类最前面的位置是一个虚函数表的指针vptr,这个指针指向一个虚函数表,表中存放所有虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承父类,也会继承其虚函数表,当子类重写父类中的虚函数时,会将其继承到的虚函数表中的地址替换为重写的函数地址。每个对象中的vptr都是独立一份的,且在该类对象被构造时被初始化。使用虚函数,会增加访问内存开销,降低效率。
虚函数表在编译期生成,存放在全局数据区,即静态区。
虚继承
虚继承是用来解决多重继承中的命名冲突和数据冗余问题。比如类C继承类B1、类B2,而类B1和类B2都是继承了A,那么在C中就有两份类A里的数据,虚继承就是,B1和B2对A都是虚继承,那么A就是虚基类。这样在派生类C中就只有一份类A中的数据。
虚继承的目的就是让类声明,愿意共享基类,这个基类就是虚基类
C和C++的区别
-
设计思想上
C++是面向对象的语言,C是面向过程的结构化编程语言
-
语法上
-
C++具有重载、继承和多态三种特性
-
C++相比C,增加许多类型安全的功能,比如强制类型转换
-
C++支持范式编程,比如模板类、函数模板类等
-
C和C++的调用
C调用C++代码
C调用C++,需要把C++的类使用C接口封装后,再调用。使用extern "C"是告诉编译器依照C的方式来编译封装接口,当然接口函数里的C++语法还是按C++方式编译,这样编译的函数就不会带参数。
C++调用C代码
C++调用C,使用extern "C"是让C++连接器找调用函数的符号时采用C的方式,这样C++连接器就能过只有函数名的函数。
C++中四种cast转换
-
const_cast
用于将const变量转为非const
-
static_cast
-
用于各种隐式转换,比如非const转为const,void*转指针等
-
用于多态向上转化,向下转能成功但是不安全,并且结果未知
比如short->int,int->double风险较低
-
-
dynamic_cast
用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上(和向下)转换。只能转指针或引用类型。对于指针,如果转换失败将返回NULL,对于引用,如果转换失败将抛出
std::bad_cast
异常。向下转换:指的是子类向基类的转换
向上转换:指的是基类向子类的转换
它通过判断执行到该语句时,变量的运行类型和要转换的类型是否相同来判断是否能够进行向下转换。
-
reinterpret_cast
仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,非常简单粗暴,几乎什么都可以转,比如将int转指针,所以风险很高。
为什么不使用C的强制转换?
转换不明确,不能进行错误检查,容易出错。
const_cast <new_type>(expression)
static_cast <new_type>(expression)
dynamic_cast <new_type>(expression)
reinterpret_cast <new_type>(expression)
const int constant = 21;
int* modifier = const_cast<int*>(&constant);
*modifier = 7;
/*
constant = 21
modifier = 7
指向同一个地址
*/
C/C++中指针和引用的区别
- 指针有自己的一块空间,引用只是一个别名
- 指针的大小是4(32位)/8(64位)个字节,引用则是被引用对象的大小
- 指针可以为空,引用初始化必须绑定对象
- 作为参数传递时,指针需要被解引用才可以对对象进行操作,引用直接修改便会改变引用指向的对象
- 有const指针,没有const引用
- 指针在使用中可以改变指向对象,引用只能是一个对象的引用,不能改变
- **指针可以有多级指针(p),引用只有一级
- 指针使用++运算符是地址+1,引用使用++运算符是引用的对象值+1
- 返回动态内存分配的对象或者内存必须使用指针,引用可能引起内存泄漏
C++的smart pointer四个智能指针
四个智能指针:auto_ptr,shared_ptr,weak_ptr,unique_ptr。后三个是C++11支持的,第一个已经被C++11弃用了。
-
auto_ptr(C++98的方案,C++11已经抛弃)
采用所有权模式。
auto_ptr<string>p1 (new string("I reigned lonely as a cloud.")); auto_ptr<string>p2; p2 = p1;//不会报错
p2剥夺了p1的所有权,当程序运行时访问p1将会报错,这就展现出auto_ptr的缺点:存在潜在的内存崩溃问题!
-
unique_ptr(替换auto_ptr)
unique_ptr是独占资源所有权的指针,保证同一时间内只有一个智能指针可以指向该对象。对于避免资源泄漏特别有用。(new创建对象后因为发生异常没有调用delete)
采用所有权模式。
unique_ptr<string>p3 (new string("I reigned lonely as a cloud.")); unique_ptr<string>p4; p4 = p3;//会报错
认为p4=p3非法,避免p3不再指向有效数据的问题。所以unique_ptr比auto_ptr更安全。
当程序试图将一个unique_ptr赋值给另一个,如果源unique_ptr是个临时右值,允许;如果源unique_ptr将存在一段时间,禁止。
unique_ptr<string>pu1 (new string("hello")); unique_ptr<string>pu2; pu2 = pu1;//#1 not allowed unique_ptr<string>pu3; pu3 = unique_ptr<string>(new string("world"));//#2 allowed
#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它会调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权让给pu3后会被销毁。
如果想执行#1的操作,要安全的重用这种指针,可以给它赋新值。C++中有一个标准库函数std::move(),能够将一个unique_ptr赋给另一个。
unique_ptr<string>p1,p2; p1 = unique_ptr<string>(new string("hello")); p2 = move(p1);//此时p1未赋值,只是转移,没有任何拷贝的意思 p1 = unique_ptr<string>(new string("world")); cout<<*p1<< ' '<<*p2<<'\n';//输出:hello world
-
shared_ptr
shared_ptr是共享资源所有权的指针。多个智能指针可以指向相同对象,该对象和其相关资源会在最后一个引用被销毁时释放。使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr,unique_ptr,weak_ptr,shared_ptr来构造。当我们调用reset()时,当前指针会释放资源所有权,计数减一。当计数=0时资源会被释放。
shared_ptr是为了解决auto_ptr在对象所有权上的局限性(auto_ptr是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针。
成员函数:
use_count 返回引用计数的个数
unique 返回是否独占所有权(user_count=1)
swap 交换两个shared_ptr对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更,会引起原有对象引用计数的减少
get 返回内部对象(指针),由于已经重载了方法,因此和直接适用对象是一样的,如shared_ptrsp(new int(1));sp与sp.get()是等价的。
shared_ptr<string>p1(new string("hello")); shared_ptr<string>p2(p1); cout<<*p1.get()<<'\n'; cout<<p1.use_count()<<'\n'; p1.reset(); cout<<p2.use_count()<<'\n'; if(p2.unique())cout<<"lonely"<<'\n'; else cout<<"not lonely"<<'\n'; /* hello 2 1 lonely */
-
weak_ptr
weak_ptr是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象。进行该对象的内存管理是那个强引用的 shared_ptr,weak_ptr只是提供了对管理对象的一个访问手段,可以通过传入shared_ptr或weak_ptr对象构造,它的构造和析构不会引起计数的增加或减少。
成员函数
weak_ptr没有重载*和->,但可以使用lock获得一个可用的shared_ptr对象(weak_ptr在使用前需要检查合法性)
expired 用于检测所管理的对象是否已经释放,如果已经释放返回true,否则返回false
lock 用于获取所管理对象的强引用(shared_ptr),如果对象存在返回一个shared_ptr,其内部对象指向与weak_ptr相同,否则返回一个空的shared_ptr。
reset weak_ptr置空
weak_ptr支持拷贝和赋值,但不会影响shared_ptr的计数
使用 weak_ptr 解决 shared_ptr 因循环引有不能释放资源的问题
class Person{ private: string name; shared_ptr<Person>partner;//weak_ptr<Person>partner public: Person(const string& _name):name(_name){ cout<<name<<" created"<<'\n'; } virtual ~Person(){ cout<<name<<" destroyed"<<'\n'; } friend bool partnerUp(std::shared_ptr<Person>& p1, std::shared_ptr<Person>& p2) { if (!p1 || !p2) { return false; } p1->partner = p2; // weak_ptr重载的赋值运算符中可以接收shared_ptr对象 p2->partner = p1; cout << p1->name << " is now partenered with " << p2