halo~我是bay_Tong桐小白
本文内容是桐小白个人对所学知识进行的总结和分享,知识点会不定期进行编辑更新和完善,了解最近更新内容可参看更新日志,欢迎各位大神留言、指点
C++面向对象基础总结——基本知识要点汇总
【更新日志】
最近更新:
- 暂无编辑记录,内容持续更新中……
C++类和对象
C++类和对象详解
举一个简单的例子
- 类的访问修饰符若没有写明则默认为private
- 构造函数与析构函数没有显式定义时系统会调用默认的构造函数与析构函数
- 内联函数的引入目的是为了解决程序中函数调用的效率问题,通常为10行以内的在程序中会被大量重复调用的小函数。通常函数名前防止关键字inline,在类定义中的函数都是内联函数,可以省略掉inline限定符
- 友元不是成员函数,但是可以访问类中的私有成员
- 静态成员在类的所有对象中是共享的,及无论创建多少个类的对象,静态成员都只有一个副本
C++中的this指针
this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。全局经由一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址
C++中的友元与静态
友元
- 友元可以是一个函数,也可以是一个类,尽管不是类成员,但有权访问类的所有private和protected成员。友元的声明需要使用关键字friend
- 一个类的友元可以形象理解为类把这个函数或类当成自己的朋友,因此自己内部所有成员对朋友公开。但是友元不具有相互性
- 由于一个类的友元并不属于这个类,因此友元函数没有this指针,友元函数可以直接调用,不需要通过对象或指针
静态
- 静态成员在类的所有对象中是共享的。当声明类的成员为静态时,意味着无论创建多少个类的对象,静态成员都只有一个副本
- 静态成员的初始化不能放置在类的定义中,但是可以在类的外部通过使用范围解析运算符::来重新声明静态变量从而对它进行初始化。如果不存在其它的初始化语句,在创建第一个对象时,所有的静态成员都会被初始化为0
- 静态数据成员和普通数据成员的区别
- 静态成员函数与普通成员函数的区别
· 静态成员函数没有this指针,只能访问静态成员(成员变量和成员函数)
· 普通成员函数有this指针,可以访问类中的任意成员
C++拷贝构造函数
拷贝构造函数形式
拷贝构造函数,又称复制构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其形参必须是引用,但并不限制为const,一般普遍的会加上const限制(引用自百度百科)
- 拷贝构造函数一般形式
classname(const classname& obj){
//构造函数主体
}
- 使用原则:
· 对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数
· 在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符号 - 如果在类中没有显示的声明一个拷贝构造函数,那么编译器会自动生成一个默认的拷贝构造函数
- 调用拷贝构造函数的情况
深拷贝与浅拷贝
浅拷贝:浅拷贝即创建一个新对象,然后对它的数据成员逐一赋值完成拷贝,如果要拷贝的原对象属性是基本属性,拷贝就是将基本类型的值赋给新对象的对应属性,如果要拷贝的原对象属性是内存地址,拷贝的就是内存地址
指针悬挂问题:一般情况下只需要使用系统提供的浅拷贝构造函数即可,但是如果对象的数据成员包括指向堆空间的指针,则浅拷贝会造成指针悬挂问题。如在对象析构时
因此如果对象的数据成员包括指向堆空间的指针,则需要进行深拷贝
深拷贝
由于深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存,因此相比于浅拷贝它的速度较慢且花销较大
延迟拷贝
延迟拷贝是浅拷贝和深拷贝的一个组合,实际上很少会使用。
当最开始拷贝一个对象时,会使用速度较快的浅拷贝,还会使用一个计数器来记录有多少对象共享这个数据。当程序想要修改原始的对象时,它会决定数据是否被共享(通过检查计数器)并根据需要进行深拷贝。延迟拷贝从外面看起来就是深拷贝,但是只要有可能它就会利用浅拷贝的速度。当原始对象中的引用不经常改变的时候可以使用延迟拷贝。由于存在计数器,效率下降很高,但只是常量级的开销。而且,
在某些情况下, 循环引用会导致一些问题。(引用来自百度百科)
C++运算符重载和函数重载
C++允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载
重载决策:当调用一个重载函数或重载运算符时,编译器通过把使用的参数个数、类型与定义中的参数个数、类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程称为重载决策
C++函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,这些同名函数的形式参数(参数个数或类型)须不同。
【PS:不能仅通过返回类型的不同来重载函数】
class PrintData {
public:
void print(int i){
cout << "整数为:" << i << endl; }
void print(double f){
cout << "浮点数为:" << f << endl; }
void print(char c[]){
cout << "字符串为:" << c << endl; }
}
C++运算符重载
运算符重载可以通过类内部与类外部两种方式进行
- 类内部重载举例:
class Test{
private:
int x;
int y;
public:
Test operator+(Test&);
}
Test Test::operator+(Test& t){
Test result;
result.x = this->x + t.x;
result.y = this->y + t.y;
return result;
}
- 类外部重载举例:
class Test{
private:
int x;
int y;
public:
friend Test operator+(Test&, Test&);
}
Test operator+(Test& a, Test& b){
Test result;
result.x = a.x + b.x;
result.y = a.y + b.y;
return result;
}
C++继承
C++中的基类与派生类
访问控制和继承
- 派生类可以访问基类中所有的非私有成员,因此基类成员如果不想被派生类的成员函数访问,则应在基类中修改类访问修饰符为private
- 举一个例子,对于类Shape的某成员member的访问
PS: 一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数
- 基类的重载运算符
- 基类的友元函数
继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
继承问题中构造函数与析构函数的相关问题
- 构造函数与析构函数调用顺序
【即调用派生类构造函数时一定会先构造基类】
- 派生类构造函数与析构函数构造规则:
当基类的构造函数没有参数,或没有显式定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数;
当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径(基类构造函数参数表的参数通常来源于派生类构造函数的参数表)
- 派生类的析构函数
在执行派生类的析构函数时,系统会自动调用基类的析构函数,对基类的对象进行清理 - 含有对象成员的派生类的构造函数
基类与派生类同名函数的调用问题
C++规定:如果在派生类中定义了与基类成员同名的成员,则派生类成员覆盖了基类的同名成员,如下例
#include <iostream>
using namespace std;
class X {
public:
void f() {
cout << "基类函数\n"; }
};
class Y :public X {
public:
void f() {
cout << "派生类函数\n"; }
void g() {
f(); }
};
int main() {
Y obj; obj.g();
return 0;
}
编译执行结果为
若要执行基类的函数f()则需要写明类与作用域运算符。如下:
#include <iostream>
using namespace std;
class X {
public:
void f() {
cout << "基类函数\n"; }
};
class Y :public X {
public:
void f() {
cout << "派生类函数\n"; }
void g() {
X::f(); }
};
int main() {
Y obj; obj.g();
return 0;
}
编译执行结果为
C++基类与派生类间的赋值兼容规则
指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,凡是基类能够实现的功能,公有派生类都能实现。
【PS:所谓赋值仅仅指对基类的数据成员赋值】
因此可以将派生类对象的值赋给基类对象,在用到基类对象的时候可以用其派生类对象代替。具体表现在以下几个方面:
- 可以用派生类对象给基类对象赋值
- 可以用派生类对象来初始化基类对象的引用
- 派生类对象的地址可以赋给指向基类的指针
- 如果函数的形参是基类对象或基类对象的引用,再调用函数时可以用派生类对象作实参
需要注意的是:
- 声明为指向基类对象的指针可以指向它的公有派生的对象, 但不允许指向它的私有派生的对象。
- 允许将一个声明为指向基类的指针指向其公有派生类的对象, 但是不能将一个声明为指向派生类对象的指针指向其基类的对象。
C++多继承、虚继承与虚基类
多继承
· 即一个子类可以有多个父类,它继承了多个父类的特性
· 多继承可以看作是单继承的扩展,所谓多继承是指派生类具有多个基类,派生类与每个基类之间的关系仍可可作是一个单继承(引用来自百度百科)
当出现环状的多重继承关系时,如类D继承自类B与类C,类B与类C都继承类A,在实际的情况下是这样的
此情况下易发生二义性的问题,举一个例子
#include <iostream>
using namespace std;
class A {
protected:
int a;
public:
A() {
a = 5; cout << "A a=" << a << endl; }
};
class B :public A {
public:
B() {
a = a + 10; cout << "B a=" << a << endl; }
};
class C :public A {
public:
C() {
a = a + 20; cout << "C a=" << a << endl; }
};
class D :public B, public C {
public:
D() {
cout << "D a=" << a << endl; }
};
int main()
{
D obj; return 0;
}
此例中因为数据a不明确而出现错误,因此要引入虚继承
虚继承
是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类(引用自百度百科)
上述例子稍作修改即可实现虚继承,修改部分如下所示
class B :public virtual A {
public:
B() {
a = a + 10; cout << "B a=" << a << endl; }
};
class C :public virtual A {
public:
C() {
a = a + 20; cout << "C a=" << a << endl; }
};
如此类B类C对类A的继承定义为虚拟继承,而类A就成了虚基类,这样从不同路径继承的虚基类的成员在内存中就只拥有一个拷贝,对虚基类的构造函数只调用一次,且是在第一次出现时调用
C++多态
C++静态联编与动态联编
联编
联编是指一个计算机程序自身彼此关联(使一个源程序经过编译、连接,成为一个可执行程序)的过程,在这个联编过程中,需要确定程序中的操作调用(函数调用)与执行该操作(函数)的代码段之间的映射关系,按照联编所进行的阶段不同,可分为静态联编和动态联编。(引用自百度百科)
静态联编
- 定义:在程序编译连接阶段进行,即在程序开始之前就解决了程序中的操作调用与执行代码之间的关系。静态联编又称早期联编、前期联编。确定这种关系叫束定,编译时束定又称为静态束定
- 特点:调用速度快,效率高
在多继承中静态联编带来的问题
举例:
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0) {
width = a; height = b;
}
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; shape->area();
shape = &tri; shape->area();
return 0;
}
上面的代码编译执行的结果:
虽然基类中的函数area()在派生类中被重定义,但是函数映射关系在程序执行前就确定好,调用函数area()被编译器设置为基类中的版本,因此执行结果仍为基类中的函数,这就是所谓的静态多态,或静态链接
动态联编
- 定义:在编译阶段并不能确切地知道将要调用的函数执行代码,要求联编工作在程序运行时才能执行,因此又称动态关联、后期联编、晚期联编。C++规定动态联编是在虚函数(virtual)的支持下实现
- 调用方式:通过对象的指针/通过引用调用/通过成员函数调用,否则无法实现动态联编
- 特点:灵活,问题抽象性、易维护性
上述例子稍作修改即可实现动态联编,修改部分如下所示
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;
}
};
编译执行结果:
如此,在具体执行时才进行映射关系的确定,实现了多态性
C++虚函数与纯虚函数
虚函数:虚函数是在基类中使用关键字virtual声明的函数,在派生类中重新定义基类中的虚函数时,会告诉编译器在编译时不要进行静态链接,我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作就是动态链接,或后期绑定
纯虚函数:纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用
C++中的纯虚函数一般在函数签名后使用=0作为此类函数的标志
仍用上述Shape的例子进行改写:
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0) {
width = a; height = b;
}
virtual int area() = 0;
};
如此编译器被告知函数area()没有主体,是纯虚函数
C++接口(抽象类)
抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
抽象类是不完整的,它只能用作基类。在面向对象方法中,抽象类主要用来进行类型隐藏和充当全局变量的角色。(引用来自百度百科)
- 在C++中,含有纯虚函数的类称为抽象类(通常称为ABC)。
- 设计抽象类的目的是为了给其它类提供一个可以继承的适当的基类,抽象类不能被用于实例化对象,只能作为接口使用;可用于实例化对象的类被称为具体类
- 如果一个抽象类的子类需要被实例化,则必须实现每个虚函数,如果没有在派生类中重写纯虚函数就尝试实例化该类的对象,会导致编译错误
持续更新中……
我是桐小白,一个摸爬滚打的计算机小白