C++篇:001.类与对象
一、面向对象,面向过程,类的定义
面向对象和面向过程的区别
面向过程:分析解决问题所需要的步骤
面向过程:把构成问题实物分解成各个对象,描述某个事物在整个解决问题的步骤中的行为
举一个有意思的例子(番茄炒鸡蛋)
面向过程:先买鸡蛋和番茄 -> 洗番茄 -> 搅拌鸡蛋 -> 生火 -> 炒菜 -> 番茄炒鸡蛋
面向对象:告诉厨师想吃番茄炒鸡蛋 -> 厨师 -> 番茄炒鸡蛋
类与对象的基本概念
类:具有相同特性(数据元素)和行为(功能)的对象的抽象就是类
对象:人们要进行研究的任何事物,它不仅能表示具体的事物,还能表示抽象的规则、计划或事件
类class
语法
class ClassName
{
PropertyModifier:
propertoys
functions
};
class:定义类使用的关键字
ClassName:类名
PropertyModifier:属性修饰符,有public、protected、private
propertoys:属性列表(变量)
functions:函数列表
Eg.定义一个学生类,拥有打印自己姓名的方法
class Student
{
public:
void printName(string name)
{
cout << name << endl;
}
};
二、构造函数(“函数”也称“方法”)
类对象
使用类创建对象的过程,又称为类的实例化
语法
ClassName classVar(param)
//类名 类对象(参数)
class Student
{
public:
void printName(string name)
{
cout << name << endl;
}
};
int main() {
Student stu;
return 0;
}
构造函数
创建一个类时,会自动添加一个跟类名一样的函数,该函数没有返回值且不做任何操作
构造函数在创建对象时使用,每当创建一个类的实例对象,C++解释器都会自动的调用它
语法
class ClassName
{
pubic:
Classname(){}
}
class Student
{
public:
Student(){
cout << "Student()" << endl;
}
void printName(string name)
{
cout << name << endl;
}
};
构造函数参数
C++中的构造函数,默认没有参数,但我们也可以重写该函数,为其设置任意多个和任意类型的参数,如果,我们在构造函数方法里设置了参数,那么我们在实例化类对象的时候,必须要传入对应的参数(也可以设置默认参数,不传入参数时,使用默认值)
三、复制构造函数
复制构造函数只有一个参数,参数类型是本类的引用(一般用const引用)
如果类的设计者没有写复制构造函数,编译器就会自动生成复制构造函数,称为”默认复制构造函数“
默认构造函数(即无参构造函数)不一定存在,但是复制构造函数总是会存在
复制构造函数常用于
- 通过使用另一个同类型的对象来初始化新创建的对象
- 复制对象把它作为参数传递给函数
- 复制对象,并从函数返回这个对象
下面有一个案例
class Complex
{
public:
double real, imag;
Complex(double r, double i) {
real = r;
imag = i;
}
Complex(const Complex &c){
real = c.real;
imag = c.imag;
cout << "我被调用了" << endl;
}//编写了复制构造函数,则默认复制构造函数就不存在了
};
int main() {
Complex c1(1, 2);
Complex c2(c1);//用复制构造函数初始化c2
cout << c2.real << " " << c2.imag;
return 0;
}
四、析构函数 动态创建对象 new与delete
析构函数
在类被销毁时,析构函数被用来做一些清理工作,比如释放被分配的内存、关闭打开的文件(系统自动调用)
析构函数没有返回值,不能被显式调用,没有参数,不能被重载,因此一个类只能有一个析构函数,如果用户没有定义,编译器会自动生成一个默认的析构函数
语法
~Destructor()
{
}
class Student
{
public:
Student(string name, int age)
{
}
~Student()
{
cout << "Call ~Student" << endl;
}
};
new操作符
当用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化
语法
Person *person = new Person;
注意new出来的对象都是指针
delete操作符
delete只适用于由new创建的对象,如果正在删除的对象的指针是NULL,将不发生任何事,因此建议在删除指针后,立即把指针赋值为NULL,以免对它二次删除
五、动态创建对象数组
class Student
{
public:
Student(){
cout << 111 << endl;
} //栈上可以聚合初始化,但必须提供一个默认的构造函数
Student(string name, int age){
cout << name << endl;
cout << age << endl;
}
void func1(){
cout << "func1" << endl;
}
};
int main(){
//栈聚合初始化
Studnet stu[] = {{"zs", 20}, {"ww, 21"}};
//创建堆上对象数组必须提供构造函数
Student *pstu = new Student[20];
return 0;
}
六、成员变量与成员函数
C++类的内部定义的变量被称为成员变量
class ClassName
{
public:
Type paramter;
};
C++类的普通成员函数是定义在类内部的方法
class ClassName
{
public:
Type funcName(paramterType paramter)
{
}
};
七、静态成员变量与静态成员函数
在C++中,类的静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则。在访问类的静态成员时,既可以使用类名来访问也可以使用对象名来访问。
静态成员变量
静态成员变量:静态数据成员在定义或说明时,前面加关键字static
class ClassName
{
public:
static Type paramter;
};
静态成员初始化:静态成员在定义后,必须要进行初始化
Type ClassName::paramter = value;
静态成员引用:静态成员的引用可以使用类名或者对象名
ClassName::paramter
下面是一个案例
class Student
{
public:
static string course;
};
string Student::course = "C++";
int main()
{
cout << "Student::course = " << Student::course << endl;
return 0;
}
静态成员函数
静态成员函数:静态成员方法不属于任何对象,只属于类。同时,C++的静态成员函数只能访问静态成员。静态成员函数可以通过类来直接调用,编译器不会为它增加形参this,所以不管有没有创建对象,都可以调用静态成员函数。
class ClassName
{
public:
static Type funcName(paramterType paramter)
{
}
};
静态成员方法调用:
ClassName::funcName(params)
instance.funcName(params)
下面是一个案例
class Student
{
public:
Student()
{
}
static void sayHello()
{
cout << "Hello C++!" << endl;
}
};
int main()
{
Student::sayHello();
Student stu;
stu.sayHello();
return 0;
}
常成员函数
const修饰类的成员函数:常成员函数,在声明或定义时,在函数头部的结尾加上const关键字。可以访问类中所有的成员变量,但不可以修改他们的值,常成员函数主要是为了保护数据而设置的。
class ClassName
{
public:
type func() const
{
}
};
class Student
{
public:
Student(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
void sayHello() const
{
cout << "I am" << this->m_name << "and i am " << this->m_age << "years old!" << endl;
}
private:
string m_name;
int m_age;
};
int main()
{
Student stu("张三丰", 110);
stu.sayHello();
return 0;
}
八、this指针
this实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给this。
this是const指针,它的值是不能被修改的,赋值、递增、递减等都是不允许的。
this只能在成员函数内部使用,而只有当对象被创建后this才有意义,因此不能在static成员函数中使用。
class Student
{
public:
Student(string name, int age)
{
this->m_name = name;
this->m_age = age;
}
void show()
{
cout << "this = " << this << endl;
}
private:
string m_name;
int m_age;
};
int main(){
Student stu ("zsf", 120);
stu.show();
cout << "stu = " << &stu << endl;
Student stu1("zsf2", 200);
stu1.show();
cout << "stu1 = " << &stu1 << endl;
return 0;
}
九、修饰符 运算符重载
访问限定符
C++中,所有的成员变量和成员函数都有访问权限,用来控制访问权限的关键字有public、protected和private
public:可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
protected:可以被类中的函数、子类的函数、其友元函数访问,但不能被该类的对象访问
private:只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问
十、友元函数 友元类
友元函数
在C++中,类的私有成员只能在本类中中访问,类的保护成员只能在本类或者子类中访问,但类的友元函数可以访问类的私有成员和保护成员。
类的友元函数定义在类外部,尽管友元函数的原型在类的定义中出现过,但是友元函数并不是成员函数。
class ClassName
{
public:
friend void func();
};
void func()
{
}
友元类
在C++中,如果一个类是另一个类的友元类,友元类可以访问类的所有的私有成员
class ClassName
{
public:
friend class ClassName2;
};
class ClassName2
{
};
下面是一个示例
class Teacher;
class Student{
private:
string name = "王五";
int age;
public:
void stu_print(Teacher &t);
};
class Teacher{
private:
string name = "张三";
int age;
public:
friend class Student;
};
void Student::stu_print(Teacher &t){
cout << this->name << endl;
cout << t.name << endl;
}
int main(){
Student s1;
Teacher t1;
s1.stu_print(t1);
return 0;
}
十一、运算符重载
运算符重载的本质是函数重载,带有一定的规则需要遵循:
- 重载运算符时,运算符的运算顺序和优先级不变,操作数个数不变
- 不能创造新的运算符,只能重载已有的六个运算符
- 重载后的运算符的功能应当与运算符的实际意义相符
有一些运算符是不可以重载的
| 不能重载的运算符 | 含义 |
|---|---|
| . | 类属关系运算符 |
| .* | 成员指针运算符 |
| :: | 作用域运算符 |
| ?: | 条件运算符 |
| # | 编译预处理符号 |
| sizeof | 取数据类型的长度 |
运算符重载有两种形式,重载为类的成员函数和重载为类的友元函数
重载为类的成员函数
funcType operator 运算符(形参表)
{
函数体;
}
重载为类的友元函数
friend funcType operator 运算符(形参表)
{
函数体;
}
下面是一个示例
class Student{
private:
string name;
int age;
public:
Student(){
name = "zs";
age = 20;
}
Student(string name, int age){
this->name = name;
this->age = age;
}
void operator=(const Student &t){
this->name = t.name;
this->age = t.age;
}
}
十二、继承
继承
class derived-class:access-specifier base-class//在类定义时使用
derived-class:派生类,即继承自base-class创建的新类
access-specifier:继承访问修饰符(public, protected, private)
base-class:基类
三种继承方式:这一项是可写项,默认为private
| 行为基类成员,列为派生类继承方法 | public | protected | private |
|---|---|---|---|
| public继承 | public | protected | 不能使用 |
| protected继承 | protected | protected | 不能使用 |
| private继承 | private | private | 不能使用 |
子类调用父类构造函数
ChildClass::ChildClass(string name, int age, float score):SuperClass(name, age), m_score(score) {}
//构造函数,在类内使用
ChildClass c1("xiaoming", 11, 11.5);
在子类ChildClass的构造函数中,调用了父类SuperClass的构造函数,SuperClass(name, age)也可以写作name(name), age(age),SuperClass直接通过参数列表把父类的参数传给子类
参数列表中,要传入其他类的参数,就写上其他类的类名;要传入自己的参数,就直接写参数名,例如:
Xtq(string nickName, string name, char sex) : Animal(nickName), Person(name), sex(sex) {}
C++11:子类通过using来继承基类构造函数
using Base::Base
构造和析构的调用顺序
构造:父类——>子类
析构:子类——>父类
多继承
多继承:一个派生类有多个基类(一个孩子有多个父亲)
class D:public A, private B, protected C{
//类D新增加的成员
};
下面是一个示例
class Animal{
public:
Animal(){}
public:
string nickName;
};
class Person{
public:
Person(){}
public:
string name;
int age;
};
class Xtq:public Animal, public Person{
public:
Xtq(){}
void printInfo(){
cout << "昵称=" << this->nickName << "姓名=" << this->name << "年龄=" << this->age << endl;
}
private:
string sex;
};
int main(){
Xtq x1;
x1.nickName = "xt";
x1.name = "xtq";
x1.age = 10000;
x1.printInfo();
return 0;
}
菱形继承
在C++中,在使用多继承时,如果类A派生出类B和类C,类D继承自类B和类C,这时候就发生了菱形继承。
如果发生了菱形继承,这时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自A->B->D,一份来自A->C->D
解决二义性(数据冗余)问题
如果继承的两个父类内有同名的变量或者函数(数据冗余),在调用时可以加作用域解析符来区分
虚继承
面对菱形继承中出现的数据冗余问题,C++提出了虚继承,虚继承使得派生类中只保留一份间接基类的成员
class B: virtual public A{
}
class A
{
protected:
int m_a;
}
class B:virtual public A
{
protected:
intg m_b;
}
class C:virtual public A
{
protected:
intg m_c;
}
虚继承中的构造函数
注意:在虚继承中,虚基类的构造函数由最终派生类直接调用,与中间派生类无关
十三、多态
多态的概念
多态可以分为编译时的多态和运行时的多态,前者主要指函数的重载、对重载函数的调用,在编译时就你根据实参确定应该调用哪个函数,而后者则和继承、虚函数等概念有关
概念:基类指针指向基类对象,就使用基类的成员,指向派生类的对象,就使用派生类的成员。他有多种形态,这种现象称为多态
虚函数
多态时存在同名函数,使用虚函数
virtual void info()...
虚函数对多态有决定性作用,有虚函数才有多态
引用实现多态
指针和引用很像,在C++中,多态的实现,除了可以使用父类的指针指向子类的对象之外,还可以通过引用来实现多态,不过没指针灵活
Person p1("swk", 500);
Student s1("bgj", 300, 99);
Person& pper = p1;
Person& pstu = s1;
pper.info();
pstu.info();
输出结果为:
person info: swk 500
student info:bgj 300 99
下面是一个完整示例
#include <iostream>
using namespace std;
class Animal {
public:
virtual void eat(){
cout << "Animal eat" << endl;
}
};
class Dog:public Animal {
public:
virtual void eat(){
cout << "Dog eat" << endl;
}
};
class Cat:public Animal {
public:
virtual void eat(){
cout << "Cat eat" << endl;
}
};
int main() {
/*指针用法
Animal *pa = new Animal();
pa -> eat();
pa = new Dog();
pa -> eat();
pa = new Cat();
pa -> eat();
*/
/*引用用法
Animal a1;
Animal& ya1 = a1;
ya1.eat();
Dog d1;
Animal& ya2 = d1;
ya2.eat();
*/
return 0;
}
输出结果为:
Animal eat
Dog eat
虚析构
C++的构造函数不可以被声明为虚函数,但是析构函数可以,有时候也必须将析构函数声明为虚函数
用C++开发的时候,用来做基类的类的析构函数一般都是虚函数
virtual ~FuncNAme()
{}
虚函数表
如果一个类包含虚函数,那么这个类就会包含一张虚函数表,虚函数表内储存每一个虚函数的地址。对象中不存在虚函数,只存在虚指针,同一个类的所有对象都使用同一张虚函数表。
动态绑定
动态绑定的实现依赖编译器维护的虚函数表(Virtual Table,简称 vtable):
- 每个包含虚函数的类都有一个 vtable:表中存储该类所有虚函数的地址。
- 对象包含一个虚表指针(vptr):每个对象在创建时,会自动添加一个隐藏的指针(vptr),指向所属类的 vtable。
- 调用过程:当通过基类指针 / 引用调用虚函数时,程序会先通过 vptr 找到对应类的 vtable,再从表中取出函数地址并调用,从而实现运行时的动态绑定。
当子类没有重写基类虚函数时,父类指针指向子类后调用父类函数,会调用父类内的函数
当子类重写基类虚函数时,父类指针指向子类后调用此函数,会调用重写后的子类函数,以下为代码验证:
class Animal{
public:
virtual void func1(){
cout << "Animal func1" << endl;
}
virtual void func2(){
cout << "Animal func1" << endl;
}
};
class Dog:public Animal{
public:
virtual void func1(){
cout << "Dog func1" << endl;
}
virtual void func4(){
cout << "Dog func4" << endl;
}
};
int main(){
Animal* a = new Dog;
a->func1();
return 0;
}
抽象基类和纯虚函数
纯虚函数
虚函数:virtual type funcName(plist) { }
纯虚函数:virtual type funcName(plist) = 0;
抽象基类
至少有一个纯虚函数的基类称为抽象基类
#include <iostream>
using namespace std;
class Abstract {
public:
//烧水
virtual void boil() = 0;
//泡
virtual void brow() = 0;
//倒入杯子
virtual void pourInCup() = 0;
//加辅料
virtual void putSomething() = 0;
void makeDrink() {
boil();
brow();
pourInCup();
putSomething();
}
};
class Coffee:public AbstractDrinking {
public:
virtual void boil() {
cout << "煮山泉水" << endl;
}
virtual void brow() {
cout << "泡咖啡" << endl;
}
virtual void pourInCup() {
cout << "咖啡倒入杯子" << endl;
}
virtual void putSomething() {
cout << "加牛奶" << endl;
}
};
class Tea:public AbstractDrinking {
public:
virtual void boil() {
cout << "煮山泉水" << endl;
}
virtual void brow() {
cout << "泡茶" << endl;
}
virtual void pourInCup() {
cout << "茶倒入杯子" << endl;
}
virtual void putSomething() {
cout << "加盐" << endl;
}
};
void DoBussiness(AbstractDrinking* ad) {
ad->makeDrink();
delete ad;
}
int main() {
DoBussiness(new Coffee());
cout << endl;
DoBussiness(new Tea());
}
浙公网安备 33010602011771号