首先的话解释一下什么是类吧
1 2 3 4 5
| class student { string name; int age; };
|
就是带class的关键字,里面的姓名和年龄就是类的成员数据,也叫做属性,其实和C的结构体是差不多的
类中的publick和private
- publick里面的成员是可以在类的外部进行访问的
- private里面的成员是不用在类的外部访问的
先看如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <string> #include <iostream> using namespace std;
class student { public: string name = "Alice"; private: int age = 18; };
int main(){ student aa; cout << aa.name << endl; return 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
| #include <string> #include <iostream> using namespace std;
class student { public: void print_name(); private: void print_age(); };
void student :: print_name(){ print_age(); cout << "公有" << endl; }
void student :: print_age(){ cout << "私有" << endl; }
int main(){ student aa; aa.print_name();
return 0; }
|
这边其实就是private对类外部隐藏类内部的工作机制,只能在类里面看到,也就是封装性
但不是说私有方法就无法被调用了,可以通过公有方法来调用
其实上面写法都是不正规的,
重载
成员函数的重载是指一个类中可以有重名名的,但是参数不同的函数
看如下代码
1 2 3 4 5 6 7
| class student { string name = "Alice"; int age = 18;
bool set(int a); bool set(string a); };
|
编译器是可以区分这两个函数的,根据传入的参数类型而选择调用哪个函数
构造函数
- 构造函数就是当你创建类的对象的时候会自动调用
- 构造函数的名字必须和类的名字相同
- 不需要声明是什么类型,也就是没有返回值,连void也不能写
1 2 3 4 5 6 7 8 9 10 11 12
| class student { string name = "Alice"; int age = 18;
student(){ name = "Bob"; age = 16; } };
|
这样就省去了手动初始化的过程,创建对象的时候就能直接初始化
也可以在类外定义构造函数,格式如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| class student { string name = "Alice"; int age = 18; student(); };
student :: student(){ name = "Bob"; age = 16; }
|
有的是类内声明构造函数,类外实现
1 2 3 4 5 6 7 8 9 10 11 12
| class student { public: string name = "Alice"; int age = 18; student(); };
student :: student(){ name = "Bob"; age = 16; }
|
当然构造函数也是支持重载的,会根据实例化的情况决定调用哪个构造函数
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
| #include <string> #include <iostream> using namespace std;
class student { public: string name = "Alice"; int age = 18; student(); student(int a, int b); };
student :: student(){ name = "Bob1"; }
student :: student(int a, int b){ name = "Bob2"; }
int main(){ student aa; cout << aa.name << endl; student bb(5, 3); cout << bb.name << endl; return 0; }
|
- 如果类中没有定义手动定义构造函数,编译器会自己生成一个无参数的构造函数
- 但如果你定义了构造函数,就不会生成
具体可以看如下的代码
1 2 3 4 5 6 7
| class A { };
int main() { A a; }
|
1 2 3 4 5 6 7 8
| class A { public: A(int x) {} };
int main() { A a; }
|
编译器不会帮你生成 A(),因为你自己写了构造函数,它认为你要自定义构造方式。
析构函数
析构函数主要是销毁对象,释放内存数据
看如下代码
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
| #include <string> #include <iostream> using namespace std;
class student { public: string name = "Alice"; int age = 18; student(); ~student(); };
student :: student(){ name = "Bob1"; }
student :: ~student(){ cout << "删除完成" << endl; }
int main(){ student *p = new student(); delete p; return 0; }
|
释放内存实际上是delete的作用
调用delete后
所以比如说你直接调用析构函数是不能起到释放内存资源的作用的
而局部对象(非new创建)是在生命周期结束后自动调用析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> using namespace std;
class student { public: student() { cout << "构造函数被调用" << endl; } ~student() { cout << "析构函数被调用" << endl; } };
int main() { cout << "进入 main()" << endl; student s; cout << "即将结束 main()" << endl; return 0; }
|
输出
1 2 3 4
| 进入 main() 构造函数被调用 即将结束 main() 析构函数被调用
|
扩展知识点
还有一种是例如static int a = 5;
或者是字符串常量,这种既不放在堆,也不放在栈,而是放在静态/全局数据段
常成员函数const
使用const修饰的函数实现只允许读数据,不允许修改数据,主要是为了防止误操作
看如下代码
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
| #include <string> #include <iostream> using namespace std;
class student { public: string name = "Alice"; int age = 18; void read() const; };
void student :: read() const{ cout << name << endl; }
int main(){ student *p = new student(); p -> read(); delete p; return 0; }
|
输出
静态成员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 26 27 28 29 30 31 32 33
| #include <string> #include <iostream> using namespace std;
class student { public: string name = "Alice"; int age = 18; student(); static int cnt;
};
int student :: cnt = 0;
student :: student(){ cnt += 1; }
int main(){ student *p = new student();
student aa; student bb;
cout << aa.cnt << endl; cout << bb.cnt << endl; cout << student :: cnt << endl; delete p; return 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
| #include <string> #include <iostream> using namespace std;
class student { public: int age = 7; void print_name(); private: string name = "lin"; void print_age(); };
void student :: print_name(){ print_age(); cout << "公有" << endl; }
void student :: print_age(){ cout << "私有" << endl; }
class undergraduate : public student { public: string course; };
class postgraduate : public student { public: string research; };
int main(){ undergraduate aa; cout << aa.age << endl;
return 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
| #include <string> #include <iostream> using namespace std;
class student { public: int age = 7; void print_name(); student(){ age = 8; }
};
class undergraduate : public student { public: string course; undergraduate(){ cout << age << endl; age = 9; }
};
int main(){ undergraduate aa; cout << aa.age << endl; return 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
| #include <string> #include <iostream> using namespace std;
class student { public: int age = 7; void print_name(); student(int a){ age = 8; }
};
class undergraduate : public student { public: string course; undergraduate(int a) : student(a){ cout << age << endl; age = 9; }
};
int main(){ undergraduate aa(5); cout << aa.age << endl; return 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
| #include <string> #include <iostream> using namespace std;
class student { public: int age = 7; void print_name(int a){ cout << "111" << endl; }
};
class undergraduate : public student { public: string course; void print_name(string a){ cout << "222" << endl; }
};
int main(){ undergraduate aa; return 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
| #include <string> #include <iostream> using namespace std;
class student { public: int age = 7; void print_name(int a){ cout << "111" << endl; }
};
class undergraduate : public student { public: using student :: print_name; string course; void print_name(string a){ cout << "222" << endl; }
};
int main(){ undergraduate aa; aa.print_name(1); return 0; }
|
protected关键字
现在了解完类的派生继承之后,可以来了解这个protected了
- protected在没有派生继承的情况下,他的作用和private是一样的
但如果有派生继承的情况下
- 公有继承的时候,父类中除了public被继承,protected也会被继承(继承到子类中的protected),但是只能在类中使用,类外还是无法访问的
具体看如下的代码片段
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
| #include <string> #include <iostream> using namespace std;
class student { public: int age = 7; void print_name(); private: string name = "lin"; void print_age();
protected: int iq = 77; };
class undergraduate : public student { public: string course; void test(){ cout << iq << endl; } };
class postgraduate : public student { public: string research; };
int main(){ undergraduate aa; cout << aa.age << endl;
return 0; }
|
其实继承如果可以分为如下的几种
所以在保密优先级上,private最私密,protected其次,public最公开,private是无论如何都不能被继承的
类的指针
类也是有指针的
有两种写法
第一种
1 2 3 4 5 6 7
| int main(){ student *p; student aa; p = &aa; p -> print_name(1); return 0; }
|
第二种
1 2 3 4 5
| int main(){ student *p = new student; p -> print_name(1); return 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
| #include <string> #include <iostream> using namespace std;
class student { public: int age = 7; void print_name(int a){ cout << "111" << endl; }
};
class undergraduate : public student { public: using student :: print_name; string course; void print_name(string a){ cout << "222" << endl; }
};
int main(){ student *p1; undergraduate *p2; student aa; undergraduate bb; p1 = &aa; p1 = &bb; p2 = &bb;
return 0; }
|
多态与虚函数
- 加关键字virtual表示这是一个虚函数,写在声明的时候,类外实现的时候不需要写
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
| #include <string> #include <iostream> using namespace std;
class student { public: int age = 7; virtual void print_name();
};
void student :: print_name(){ cout << "111" << endl; }
class undergraduate : public student { public: virtual void print_name(){ cout << "222" << endl; }
};
class postgraduate : public student { public: virtual void print_name(){ cout << "333" << endl; }
};
int main(){ student *p; student aa; undergraduate bb; postgraduate cc; p = &aa; p -> print_name(); p = &bb; p -> print_name(); p = &cc; p -> print_name(); return 0; }
|
加了virtual才能支持多态和重写,不加virtual就只能隐藏
1 2 3 4 5 6 7 8 9 10 11 12
| class Base { public: virtual void f() { cout << "Base::f" << endl; } };
class Derived : public Base { public: void f() override { cout << "Derived::f" << endl; } };
Base* p = new Derived(); p->f();
|
1 2 3 4 5 6 7 8 9 10 11 12
| class Base { public: void f() { cout << "Base::f" << endl; } };
class Derived : public Base { public: void f() { cout << "Derived::f" << endl; } };
Base* p = new Derived(); p->f();
|
- 即使 p 实际指向的是 Derived,也不会调用子类的函数!
- 因为 没有 virtual,编译器在编译时就绑定了 Base::f()
C++多态的核心,就是只要在程序开始时候设置一个父类指针,之后这个指针可以动态的指向不同的类,并且指针还可以动态的调用不同的类的方法,从而实现了不同数据类使用相同的方法,重载是编译时决定,多态是运行时决定
纯虚函数与抽象类
具体看代码
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
| #include <string> #include <iostream> using namespace std;
class student { public: int age = 7; virtual void print_name() = 0; void stu(){ string name; }
};
class undergraduate : public student { public:
void print_name() override{ cout << "222" << endl; }
};
class postgraduate : public student { public: virtual void print_name(){ cout << "333" << endl; }
};
int main(){ student *p; undergraduate bb; return 0; }
|