C++
文章目录
面向对象
封装
-
封装是什么
隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合(C++语言中,强化了C语言的封装特性,对内开放数据,对外屏蔽数据、提供接口)
函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中,被封装的元素隐藏了它们的实现细节–可以调用一个函数但是不能够访问函数所执行的语句
-
访问限定符
- public(共有)
- protected(保护)
- private(私有)
(1)public成员可以在类外直接访问
(2)protected和private成员在类外(在继承有区别)不能够访问
(3)它们的作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
(4)class和struct不同
- 默认访问权限不同,class的默认访问权限是private,而struct的默认访问权限为public型
- 默认继承访问权限不同,class的默认继承访问权限是private,而struct的默认继承访问权限为public型
- class可用作定义模板参数的关键字,而struct不可以
举个栗子
class complex { public: complex (double r = 0, double i = 0) : re (r), im (i) { } complex& operator += (const complex&); double real () const { return re; } double imag () const { return im; } private: double re, im; friend complex& __doapl (complex*, const complex&); };
不能访问private中的数据,以下栗子为错误的
{ complex c1(2,1); cout << c1.re; cout << c1.im; }
但可以通过类提供的对外接口获取数据,以下栗子为正确的
{ complex c1(2,1); cout << c1.real(); cout << c1.imag(); }
-
this指针
在 C++ 中,成员函数在调用时会隐式地增加 this指针,指向调用它的对象,从而使用该对象的成员。this指针是所有成员函数的隐含参数,在编译阶段由编译器默默地将它添加到参数列表中。this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的。
class Box { public: // 构造函数定义 Box(double l=2.0, double b=2.0, double h=2.0) { cout <<"Constructor called." << endl; length = l; breadth = b; height = h; } double Volume() { return length * breadth * height; } int compare(Box box) { return this->Volume() > box.Volume(); } private: double length; // Length of a box double breadth; // Breadth of a box double height; // Height of a box };
{ Box Box1(3.3, 1.2, 1.5); // Declare box1 Box Box2(8.5, 6.0, 2.0); // Declare box2 if(Box1.compare(Box2)) { cout << "Box2 is smaller than Box1" <<endl; } else { cout << "Box2 is equal to or larger than Box1" <<endl; } return 0; }
-
friend
-
友元函数
在友元函数内部就可以访问该类对象的私有成员,但友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象
- 全局函数声明为友元
- 其他类的成员函数声明为友元
-
友元类
一个类 A 可以将另一个类 B 声明为自己的友元,类 B 的所有成员函数就都可以访问类 A 对象的私有成员。
- 友元的关系是单向的而不是双向的,如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员
- 友元的关系不能传递,如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类
-
继承
继承
基类、派生类
class Person
{
protected :
string _name; //姓名
string _sex; //性别
int _age; //年龄
};
// 类名,继承方式,基类
class Student :public Person
{
public:
int _No; //学号
};
1.派生对象可以赋值给基类对象
2.基类对向不能赋给派生类对象
继承方式/基类成员 | public成员 | protected成员 | private成员 |
---|---|---|---|
public继承 | public | protected | 不可见 |
protected继承 | protected | protected | 不可见 |
private继承 | private | private | 不可见 |
多继承
class D: public A, private B, protected C{
//类D新增加的成员
}
虚继承
- 虚基类的构造函数有最新派生出的类的构造函数调用
- 虚基类的构造函数优先非虚基类的构造函数执行
多态
多态按字面的意思就是多种形态,相同的方法调用,但是有不同的实现方式。多态性可以简单地概括为“一个接口,多种实现”
-
静态多态
隐式接口,编译阶段
静态多态,将同一个接口进行不同的实现,根据传入不同的参数(个数或类型不同)调用不同的实现
对于相关的对象类型,直接实现它们各自的定义,不需要共有基类,甚至可以没有任何关系。只需要各个具体类的实现中要求相同的接口声明,这里的接口称之为隐式接口。客户端把操作这些对象的函数定义为模板,当需要操作什么类型的对象时,直接对模板指定该类型实参即可
-
动态多态
显示接口,运行阶段
动态多态,则不论传递过来的哪个类的对象,函数都能够通过同一个接口调用到各自对象实现的方法。
对于相关的对象类型,确定它们之间的一个共同功能集,然后在基类中,把这些共同的功能声明为多个公共的虚函数接口。各个子类重写这些虚函数,以完成具体的功能。客户端的代码(操作函数)通过指向基类的引用或指针来操作这些对象,对虚函数的调用会自动绑定到实际提供的子类对象上去。
条件:
- 基类中必须包含虚函数,并且派生类中一定要对基类中的虚函数进行重写
- 通过基类对象的指针或者引用调用虚函数
class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } virtual int area() { cout << "Parent class area :" <<endl; return 0; } }; class Rectangle: public Shape{ public: Rectangle( int a=0, int b=0):Shape(a, b) { } int area () { cout << "Rectangle class area :" <<endl; return (width * height); } }; class Triangle: public Shape{ public: Triangle( int a=0, int b=0):Shape(a, b) { } int area () { cout << "Triangle class area :" <<endl; return (width * height / 2); } }; int main( ) { Shape *shape; Rectangle rec(10,7); Triangle tri(10,5); // 存储矩形的地址 shape = &rec; // 调用矩形的求面积函数 area shape->area(); // 存储三角形的地址 shape = &tri; // 调用三角形的求面积函数 area shape->area(); return 0; }
在使用基类指针或引用指向子类对象时,调用的函数是子类中重写的函数,这样就实现了运行时函数地址的动态绑定,即动态联编。动态多态是通过“继承+虚函数”来实现的,只有在程序运行期间(非编译期)才能判断所引用对象的实际类型,根据其实际类型调用相应的方法。具体格式就是使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,并且派生类需要重新实现该成员函数,编译器将实现动态绑定。
模板
template <typename T>
class complex
{
public:
complex (T r = 0, T i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
T real () const { return re; }
T imag () const { return im; }
private:
T re, im;
friend complex& __doapl (complex*, const complex&);
};
{
complex<double> c1(2.5,1.5);
complex<int> c2(2,6);
...
}
-
inline
定义在类中的成员函数默认都是内联的
如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的
inline仅是一个对编译器的建议,最后能否真正内联,看编译器的意思
-
初始化列表
complex(T r = 0, T i = 0) : re(r), im(i) { }
与
complex (double r = 0, double i = 0) { re = r; im = i; }
都可以对类的成员变量进行初始化,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但对于class类型来说,使用初始化列表少了一次调用默认构造函数的过程,会更高效
STL
序列容器
vector
vector容器表示一个在必要时可自动增加容量的数组。只能在矢量容器的末尾添加新元素
优点:
-
对一块不指定内存大小的数组进行连续存储,即可以像数组一样操作,但可以对此数组进行动态操作。通常体现在push_back() pop_back()
-
随机访问方便,即支持[ ]操作符和vector.at()
-
节省空间。
缺点:
-
在内部进行插入删除操作效率低
-
只能在vector的最后进行push和pop,不能在vector的头进行push和pop。
-
当动态添加的数据超过vector默认分配的大小时要进行整体的重新分配、拷贝与释放
list
list容器是一个双向链表
优点:
-
不使用连续内存完成动态操作
-
在内部方便的进行插入和删除操作
-
可在两端进行push、pop
缺点:
-
不能进行内部的随机访问,即不支持[ ]操作符和vector.at()
-
相对于verctor占用内存多
vector && list 区别
-
vectorlist
vector封装了数组
vector使用连续内存存储,支持[]运算符
vector对于随机访问的速度很快,但在头部插入元素速度很慢,在尾部插入速度很快
-
list
list封装了链表
list是以链表形式实现的,不支持[]
list对于随机访问速度慢得多,但对于插入就快的多了
deque
deque容器实现一个双端队列。它等价于一个矢量,不过增加了向容器开头添加元素的能力
优点:
-
随机访问方便,即支持[ ]操作符和vector.at()
-
在内部方便的进行插入和删除操作
-
可在两端进行push、pop
缺点:
- 占用内存多
关联容器
map && unordered_map
map<K, T>是一个关联容器,映射中的每个键的值必须唯一
multimap<K, T>容器,映射中的每个键的值不需要唯一
-
map
优点:
有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
红黑树,内部实现一个红黑书使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率非常的高缺点:
空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间
适用处:
对于那些有顺序要求的问题,用map会更高效一些
-
unordered_map
优点:
因为内部实现了哈希表,因此其查找速度非常的快
缺点:
哈希表的建立比较耗费时间
适用处:
对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map
-
使用
unordered_map的用法和map是一样的,提供了 insert,size,count等操作,并且里面的元素也是以pair类型来存贮的。其底层实现是完全不同的,上方已经解释了,但是就外部使用来说却是一致的。