C++
封装
封装
将函数定义到结构体内部,就是封装
类
带有函数的结构体称为类
成员函数
结构体里面的函数,称为成员函数
this关键字
当在一个类中,有一个成员函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct MyStruct { int a; int b; int c; int d; int init(int a, int b, int c, int d) { a = a; b = b; c = c; d = d; } }
|
当用a=a
时,编译器不能知道a是参数a还是类中的a。
所以用this关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct MyStruct { int a; int b; int c; int d; int init(int a, int b, int c, int d) { this->a = a; this->b = b; this->c = c; this->d = d; } }
|
因为this关键字指向的是结构体的首地址,所以this->a就指的是这个成员函数所在的类中的一个成员变量,而不是成员函数中的参数
总结:1.this指针时编译器默认传入的,通常会使用ECX寄存器进行传递
2.成员函数都有this指针,无论是否使用,都会有这个指针
3.this指针不能做加减运算,也不能被重新赋值
4.this指针不占用结构体的宽度
构造函数与析构函数
构造函数
构造函数不能有返回值,构造函数的名字跟类名相同
有构造函数时,当创建一个对象时,构造函数会直接被调用
总结:
1.与类名同名并且没有返回值
2.创建对象时执行,主要用于初始化
3.可以有多个相同的构造函数,最好是有一个无参的,称为函数重载,其他的函数也可以重载
4.编译器不需求必须提供,但是提供时必须不能加返回值
析构函数
析构函数也不允许写返回值类型,析构函数只能写一个,不能重载,并且必须无参,不能带参数
构造函数是创建对象的时候执行,而析构函数是当这个对象被销毁时才执行
也就是当函数要return返回值时,析构函数会被执行
总结:
1.只能有一个析构函数,不能重载
2.不能带任何参数
3.不能带返回值
4.主要用于清理工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct Person { int age; int level; char* arr; Person(int age, int level) { this->age = age; this->level = level; arr = (char*)malloc(1024); } ~Person() { printf("析构函数执行了..."); free(arr) } }
|
5.编译器不要求必须提供
继承
有一个Teacher类,一个Person类。
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct Person { int age; int gender; }
struct Teacher { int age; int gender; int level; int teacherID; }
|
发现Teacher类中有两个和Person类中是一样的,为了减少代码的重复,可以让Teacher继承Person的一些性质(age、gender)。也就是:
1 2 3 4 5
| struct Teacher:Person { int level; int teacherID; }
|
这里,Person类称为父类或基类;Teacher类称为子类或者派生类
但是,当父类和子类中有相同的数据时,比如:
1 2 3 4 5 6 7 8 9 10
| struct Person { int age; int gender; } struct Teacher:Person { int age; int level; }
|
当Teacher类继承了Person类时,其中的age重复了,但是编译器仍然会给这两个age都开辟空间
当使用时:
1 2 3 4 5 6 7
| int main(void) { Teacher t; t.Person::age = 20; t.age = 25;
}
|
继承不仅限于父类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct X { int a; int b; } struct Y:X { int c; int d; } struct z:Y { int e; int f; }
|
当使用时,Z类中能够继承到所有的a,b,c,d
多重继承
与上面类似:
1 2 3 4 5
| struct Z:X,Y { int e; int f; }
|
多重继承增加了程序的复杂度,不建议使用。
在堆中创建对象
堆中创建对象的两种方式:
1 2
| Person* p = (Person*)malloc(sizeof(Person)*10); Person* p = new Person[10];
|
释放对象占用内存对应的两种方式:
当使用malloc函数分配空间时,创建对象后不会调用构造函数,使用free()函数后也不会调用析构函数。
当使用new关键字创建对象时,会调用构造函数,并在使用delete释放对象占用内存时会调用析构函数。
面向对象程序设计之封装和继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| #include <stdio.h> #include <string.h> #pragma warning(disable:26495)
class Person { private: int Age; int Gender; public: Person() {
} Person(int Age, int Gender) { this->Age = Age; this->Gender = Gender; } void setAge(int Age) { if (Age <= 0) { this->Age = 0; } else { this->Age = Age; } } int getAge() { return this->Age; }
void setGender(int Gender) { if (Gender <= 0) { this->Gender = 0; } else { this->Gender = Gender; } } int getGender() { return this->Gender; } };
class Teacher:public Person { private: int Level;
public: Teacher() {
} Teacher(int Level, int Age, int Gender):Person(Age, Gender) { this->Level = Level; }
void setLevel(int Level) { if (Level <= 0) { this->Level = 0; } else { this->Level = Level; } } int getLevel() { return this->Level; }
};
int main(void) { Teacher* A = new Teacher(5, 20, 1); Teacher* B = new Teacher(4, 22, 0); printf("A => Age:%d,Level:%d,Gender:%d\n", A->getAge(), A->getLevel(), A->getGender()); printf("B => Age:%d,Level:%d,Gender:%d\n", B->getAge(), B->getLevel(), B->getGender()); A->setAge(50); A->setLevel(8); B->setAge(45); B->setLevel(9); putchar('\n'); printf("A => Age:%d,Level:%d,Gender:%d\n", A->getAge(), A->getLevel(), A->getGender()); printf("B => Age:%d,Level:%d,Gender:%d\n", B->getAge(), B->getLevel(), B->getGender()); }
|
最终输出:
1 2 3 4 5
| A => Age:20,Level:5,Gender:1 B => Age:22,Level:4,Gender:0
A => Age:50,Level:8,Gender:1 B => Age:45,Level:9,Gender:0
|
多态
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <stdio.h>
class Square //这是一个抽象类,抽象类不能创建对象 { public: virtual float CalcSq() = 0; };
class Circle : public Square { private: float r; public: Circle(float r) { this->r = r; } virtual float CalcSq() { return 3.14 * r * r; } };
class Rectangle : public Square { private: float x, y; public: Rectangle(float x, float y) { this->x = x; this->y = y; } virtual float CalcSq() { return x * y; } };
float CalcSq(Square* S) { return S->CalcSq(); }
int main() { Circle C(2.5); Rectangle R(4.5, 10);·
printf("Circle:\t\t%f\n", CalcSq(&C)); printf("Rectangle:\t%f\n", CalcSq(&R));
return 0; }
|
虚函数
<1>
虚函数目的是提供一个统一的接口,被继承的子类重载,以多态的形式被调用。
<2>
如果基类中的函数没有任何实现的意义,那么可以定义成纯虚函数:virtual返回类型函数名(参数列表)=0;
<3>
含有纯虚函数的类被称为抽象类(abstract class),不能创建对象。
<4>
虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用。
运算符重载

运算符重载也就是给之前的运算符重新起个名字,比如重载一个++
,使它被使用时一次+5
当然,这个重载仅在这个类中有用,原本的++
还是自增1
模板
使用template <class T>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <stdio.h> #include <stdlib.h>
template <class T> void BubbleSort(T arr[], int length) { int i; int j; for (i = 0; i < length; i++) { for (j = 0; j < length - i - 1; j++) { if (arr[j]>arr[j+1]) { T temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
int main(void) { int arr1[] = { 5,1,3,2,4,8,9,12,7,8,5,10,6,2,9 }; float arr2[] = { 5.2,1.1,3.6,2.5,4.9,8.0,9.4,1.52,7.7,8.2,5.9,10.4,6.012,9.5 }; BubbleSort<int>(arr1, sizeof(arr1)/sizeof(int)); BubbleSort<float>(arr2, sizeof(arr2)/sizeof(float)); return 0; }
|
当程序同为冒泡排序,只是数组的数据类型不同时,可以使用模板替换其中的一部分。
模板可以替换各种类型。如果类型时自己定义的,比如定义一个Number类,要给Number类中的成员进行冒泡排序,则需要先运算符重载,使>
可以比较Number中成员的大小。
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <stdio.h> #include <stdlib.h>
template <class T> void BubbleSort(T arr[], int length) { int i; int j; for (i = 0; i < length; i++) { for (j = 0; j < length - i - 1; j++) { if (arr[j] > arr[j + 1]) { T temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
class Number { private: int x;
public: Number(int x) { this->x = x; }
bool operator>(Number& num) { return this->x > num.x; } };
int main(void) { int arr1[] = { 5,1,3,2,4,8,9,12,7,8,5,10,6,2,9 }; BubbleSort<int>(arr1, sizeof(arr1) / sizeof(int));
Number arr2[] = { Number(5), Number(1), Number(3), Number(2), Number(4), Number(8), Number(9), Number(12), Number(7), Number(8), Number(5), Number(10), Number(6), Number(2), Number(9) }; BubbleSort<Number>(arr2, sizeof(arr2) / sizeof(Number));
return 0; }
|
纯虚函数
什么是纯虚函数
< 1 > 将成员函数声明为virtual
< 2 >该函数没有函数体(后跟=0)
如:
1 2 3 4 5
| class CBank { public: virtual double(返回值) GetAnnualRate(函数名)()(参数列表) = 0; }
|
抽象类
< 1 >含有纯虚函数的类,称为抽象类(Abstract Class)
< 2 >抽象类也可以包含普通成员函数
< 3 >抽象类不能实例化
如使用以下方式定义一个对象:
1 2
| CBank bank; CBank* pBank = new CBank;
|
此时编译器会报错cannot instantiate abstract class due to folowing members(无法实例化一个抽象类)
纯虚函数导致了抽象类的存在,抽象类则可以定义规范、定义一些规则
比如以下案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #include <stdio.h> #include <windows.h>
class CBank { public: virtual double GetAnnualRate() = 0; };
class ICBCBank :public CBank { private: double m_dPrincipal; public: ICBCBank(double dPrincipal) { m_dPrincipal = dPrincipal; } double GetAnnualRate() { return 0.010; } double GetTotalMoney() { return m_dPrincipal + m_dPrincipal * GetAnnualRate(); } };
class CCBank :public CBank { private: double m_dPrincipal; public: CCBank(double dPrincipal) { m_dPrincipal = dPrincipal; } double GetAnnualRate() { return 0.015; } double GetTotalMoney() { return m_dPrincipal + m_dPrincipal * GetAnnualRate(); } };
void ShowAnnualRate(CBank* pBank[], DWORD nLength) { for (int i = 0; i < nLength; i++) { printf("%.5lf \n", pBank[i]->GetAnnualRate()); } }
int main(void) { ICBCBank icbc(10000.0); double dMoney1 = icbc.GetTotalMoney(); CCBank ccb(10000.0); double dMoney2 = ccb.GetTotalMoney();
CBank* pBank[] = { &icbc,&ccb }; ShowAnnualRate(pBank, 2);
return 0; }
|
在这个案例中,银行都继承了CBank这个抽象类,这个抽象类中的纯虚函数定义了子类应该如何获取年利率,因此子类需要重写这个函数。
而它存在的意义就是,当我们需要获取所有的银行的年利率时,有一个这样统一的规范可以将所有银行的年利率统一起来,使用void ShowAnnualRate(CBank* pBank[], DWORD nLength)
来让父类指向子类。
这都是站在使用的角度来说。
对象拷贝-拷贝构造函数
拷贝构造函数
拷贝构造函数时,如果子类有继承的父类对象,父类也会被继承过来
浅拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <stdio.h> #include <string.h>
class Object { private: int m_nLength; char* m_strBuffer; public: Object() {} Object(const char* str) { m_nLength = strlen(str) + 1; m_strBuffer = new char[m_nLength]; memset(m_strBuffer, 0, m_nLength); strcpy(m_strBuffer, str); } ~Object() { delete[] m_strBuffer; } };
int main(void) { Object Obj("Hello World"); Object newObj(Obj);
return 0; }
|
像这样拷贝一个对象,在类中传递了一个指针,当拷贝这个指针时,不会拷贝指针指向地址中存储的数据,而是会拷贝这个指针。
所以当原对象空间被释放时,会把后来拷贝的对象的空间一块释放掉,导致程序错误
这个程序拷贝完之后,发现拷贝后类中的字符串指向的地址是相同的
1 2 3
| newObj {m_nLength=12 m_strBuffer=0x00c8a640 "Hello World" } Object
Obj {m_nLength=12 m_strBuffer=0x00c8a640 "Hello World" } Object
|
这两个字符串指向的是同一个地址0x00c8a640
,当第一个对象地址释放时,拷贝的对象中的字符串也会被释放。
深拷贝
基于上面这种情况,要自己写一个拷贝构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <stdio.h> #include <string.h> class Object { private: int m_nLength; char* m_strBuffer; public: Object() {} Object(const char* str) { m_nLength = strlen(str) + 1; m_strBuffer = new char[m_nLength]; memset(m_strBuffer, 0, m_nLength); strcpy(m_strBuffer, str); } Object(const Object& obj) { m_nLength = obj.m_nLength; m_strBuffer = new char[m_nLength]; memset(m_strBuffer, 0, m_nLength); strcpy(m_strBuffer, obj.m_strBuffer); } ~Object() { delete[] m_strBuffer; } };
int main(void) { Object Obj("Hello World"); Object newObj(Obj);
return 0; }
|
这时候再看拷贝后的地址
1 2
| Obj {m_nLength=12 m_strBuffer=0x013ca640 "Hello World" } Object newObj {m_nLength=12 m_strBuffer=0x013ca678 "Hello World" } Object
|
一个是0x013ca640
,一个是0x013ca678
,这样当第一个释放空间的时候,第二个拷贝的对象不受影响。
内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <stdio.h>
class OutClass { private: int a; int b;
public: class InnerClassPrivate { private: int x; int y; public: InnerClassPrivate(){} InnerClassPrivate(int x, int y) { this->x = x; this->y = y; } }; OutClass() {} OutClass(int a, int b) { this->a = a; this->b = b; } void Fn() { InnerClassPrivate In(1, 2); } };
int main(void) { OutClass Out(5, 6); OutClass::InnerClassPrivate(7, 8);
return 0; }
|
内部类就是定义在一个类内部的类,可以将内部类定义在外部类的public中,从外部使用OutClass::InnerCLass();
可以进行访问。
如果内部类只在这个外部类中使用,则可以定义在这个类的private中,让外部无法访问或者也可以提供使用函数。内部类也可以定义在函数中,比如定义在OutClass的Fn()中。
内部类不会占用外部类的空间。外部类有int a; int b;
,其中内部类中有int x
,则外部类的大小仍然是8字节。
namespace命名空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <stdio.h>
namespace ns1 { int a; int Fn(int x) { return x + 1; } class NameSpace { private: int i; int j; NameSpace() {} NameSpace(int i, int j) { this->i = i; this->j = j; } }; }
namespace ns2 { int a; int Fn(int x) { return x + 1; } class NameSpace { private: int i; int j; NameSpace() {} NameSpace(int i, int j) { this->i = i; this->j = j; } }; }
int main(void) { ns1::a = 0; printf("%d\n", ns2::Fn(8));
return 0; }
|
命名空间中的变量、函数、类等的名称可以相同,当调用时,用namespace::成员名
来使用。
如果是创建了一个类文件,在头文件中声明后,可以用using namespace 命名空间来让整个程序直接使用指定命名空间中的成员。
static关键字
含义
1.
static关键字修饰的变量相当于一个私有的全局变量
比如有这样的一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| `class staClass { private: public : static int a; int b; int c; staClass() {} staClass(int b, int c) { this->b = b; this->c = c;
} };
|
其中变量a用static修饰,所以在staClass这个类中,a是一个属于staClass私有的全局变量,可以直接进行访问,但是不能从外部访问。
2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <stdio.h>
class Demo { public : static int z; int a; int b; Demo() {} Demo(int a, int b) { this->a = a; this->b = b; } int SetZ(int z) { this->z = z; return z; } int GetZ() { return z; } };
int Demo::z;
int main(void) { Demo d1(1, 2); Demo d2(1, 2);
int temp = d1.SetZ(5); int temp2 = d2.GetZ();
return 0;
}
|
这段代码中,int z用static修饰,所以z属于这个类中的全局变量,当使用d1对象访问和使用d2对象访问时,访问的是同一个z的地址。
static单子模式
当要限制创建一个对象时,可以使用static函数进行限制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <stdio.h>
class One { private: One() {} static One* SetUpPoint; public: static One* Setup() { if (SetUpPoint == NULL) SetUpPoint = new One(); return SetUpPoint; } };
One* One::SetUpPoint = NULL;
int main(void) { One* p1 = One::Setup(); One* p2 = One::Setup();
return 0; }
|
面向对象设计中的static之静态成员函数:
总结:
1 2 3 4 5 6 7
| <1>出现在类体外的函数定义不能指定关键字static; <2>静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数; <3>非静态成员函数可以任意地访问静态成员函数和静态数据成员; <4>静态成员函数不能访问非静态成员函数和非静态数据成员; <5>调用类的静态成员函数的两种方式; <类名>::<静态成员函数名>(<参数表>) <对象名>.<静态成员函数名>(<参数表>)
|