C++篇:001

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引用)

如果类的设计者没有写复制构造函数,编译器就会自动生成复制构造函数,称为”默认复制构造函数“

默认构造函数(即无参构造函数)不一定存在,但是复制构造函数总是会存在

复制构造函数常用于

  1. 通过使用另一个同类型的对象来初始化新创建的对象
  2. 复制对象把它作为参数传递给函数
  3. 复制对象,并从函数返回这个对象

下面有一个案例

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;
}

十一、运算符重载

运算符重载的本质是函数重载,带有一定的规则需要遵循:

  1. 重载运算符时,运算符的运算顺序和优先级不变,操作数个数不变
  2. 不能创造新的运算符,只能重载已有的六个运算符
  3. 重载后的运算符的功能应当与运算符的实际意义相符

有一些运算符是不可以重载的

不能重载的运算符 含义
. 类属关系运算符
.* 成员指针运算符
:: 作用域运算符
?: 条件运算符
# 编译预处理符号
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):

  1. 每个包含虚函数的类都有一个 vtable:表中存储该类所有虚函数的地址。
  2. 对象包含一个虚表指针(vptr):每个对象在创建时,会自动添加一个隐藏的指针(vptr),指向所属类的 vtable。
  3. 调用过程:当通过基类指针 / 引用调用虚函数时,程序会先通过 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());
}
posted on 2025-10-06 10:57  Khalilll  阅读(12)  评论(0)    收藏  举报