halo~我是bay_Tong桐小白
本文内容是桐小白个人对所学知识进行的总结和分享,知识点会不定期进行编辑更新和完善,了解最近更新内容可参看更新日志,欢迎各位大神留言、指点
最近更新:
this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。全局经由一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址
友元
静态
拷贝构造函数,又称复制构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其形参必须是引用,但并不限制为const,一般普遍的会加上const限制(引用自百度百科)
classname(const classname& obj){
//构造函数主体
}
浅拷贝:浅拷贝即创建一个新对象,然后对它的数据成员逐一赋值完成拷贝,如果要拷贝的原对象属性是基本属性,拷贝就是将基本类型的值赋给新对象的对应属性,如果要拷贝的原对象属性是内存地址,拷贝的就是内存地址
指针悬挂问题:一般情况下只需要使用系统提供的浅拷贝构造函数即可,但是如果对象的数据成员包括指向堆空间的指针,则浅拷贝会造成指针悬挂问题。如在对象析构时
因此如果对象的数据成员包括指向堆空间的指针,则需要进行深拷贝
深拷贝
由于深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存,因此相比于浅拷贝它的速度较慢且花销较大
延迟拷贝
延迟拷贝是浅拷贝和深拷贝的一个组合,实际上很少会使用。
当最开始拷贝一个对象时,会使用速度较快的浅拷贝,还会使用一个计数器来记录有多少对象共享这个数据。当程序想要修改原始的对象时,它会决定数据是否被共享(通过检查计数器)并根据需要进行深拷贝。延迟拷贝从外面看起来就是深拷贝,但是只要有可能它就会利用浅拷贝的速度。当原始对象中的引用不经常改变的时候可以使用延迟拷贝。由于存在计数器,效率下降很高,但只是常量级的开销。而且,
在某些情况下, 循环引用会导致一些问题。(引用来自百度百科)
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;
}
继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
基类与派生类同名函数的调用问题
C++规定:如果在派生类中定义了与基类成员同名的成员,则派生类成员覆盖了基类的同名成员,如下例
#include
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
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;
}
指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,凡是基类能够实现的功能,公有派生类都能实现。
【PS:所谓赋值仅仅指对基类的数据成员赋值】
因此可以将派生类对象的值赋给基类对象,在用到基类对象的时候可以用其派生类对象代替。具体表现在以下几个方面:
需要注意的是:
多继承
· 即一个子类可以有多个父类,它继承了多个父类的特性
· 多继承可以看作是单继承的扩展,所谓多继承是指派生类具有多个基类,派生类与每个基类之间的关系仍可可作是一个单继承(引用来自百度百科)
当出现环状的多重继承关系时,如类D继承自类B与类C,类B与类C都继承类A,在实际的情况下是这样的
此情况下易发生二义性的问题,举一个例子
#include
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就成了虚基类,这样从不同路径继承的虚基类的成员在内存中就只拥有一个拷贝,对虚基类的构造函数只调用一次,且是在第一次出现时调用
联编
联编是指一个计算机程序自身彼此关联(使一个源程序经过编译、连接,成为一个可执行程序)的过程,在这个联编过程中,需要确定程序中的操作调用(函数调用)与执行该操作(函数)的代码段之间的映射关系,按照联编所进行的阶段不同,可分为静态联编和动态联编。(引用自百度百科)
在多继承中静态联编带来的问题
举例:
#include
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()被编译器设置为基类中的版本,因此执行结果仍为基类中的函数,这就是所谓的静态多态,或静态链接
动态联编
上述例子稍作修改即可实现动态联编,修改部分如下所示
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;
}
};
编译执行结果:
如此,在具体执行时才进行映射关系的确定,实现了多态性
虚函数:虚函数是在基类中使用关键字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()没有主体,是纯虚函数
抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。
抽象类是不完整的,它只能用作基类。在面向对象方法中,抽象类主要用来进行类型隐藏和充当全局变量的角色。(引用来自百度百科)
持续更新中……
我是桐小白,一个摸爬滚打的计算机小白