c++类和对象【通俗易懂】

六、面向对象

面向对象:是一种看待问题解决问题的思维方式,着眼点在于问题是如何一步步解决的,然后亲力亲为的解决问题

面向过程:是一种看待问题解决问题的思维方式,着眼点在于找到一个能解决问题的实体,让实体解决问题

6.1、类与对象

类是对象的集合,对象是类的个体,并且类是自定义类型的数据

权限修饰:

  • private:私有的,默认权限,只能在当前的类中进行访问

  • protected:保护权限,只能在本类或者子类中进行访问

  • public:公共权限,可以在任意位置进行访问

1)类的创建:

从若干相同对象中抽取相同的特征和行为,设计为一个类,类中定义所有对象共同的特征和行为,其中特征用属性表示,行为用方法表示,类中定义的属性和方法默认都是private修饰的

/* 格式:
 *      class 类名{
 *          属性
 *          方法
 *      }
 * 
 * 设计一个类,描述人
 * 属性:姓名,性别,年龄
 * 方法:走路,吃法
 */
class Person{
// 修改权限为public
public:
    // 定义属性
    string name;
    string gender;
    int age;
​
    // 定义方法
    void walk(){
        cout << "走路!" << endl;
    }
    void eat(){
        cout << "吃饭" << endl;
    }
};

2)创建对象:

// 格式1:类名 对象名 = 类名();
Person xiaoming = Person();                   // 在栈上开辟空间,没有初始化值
​
// 格式2: 类名* 对象名 = new 类名();
Person* xiaoming = new Person();              // 在堆上开辟空间,有初始化值

注意:开辟的空间大小取决于类中成员变量的大小,如果没有属性,对象占用的空间不是0 而是1,this表示指向当前对象的指针

6.2、成员访问

需要使用对象访问类中属性和方法

// 在栈上创建的对象调用语法: 对象名.属性值 | 对象名.方法名
// 在堆上创建的对象调用语法:对象名->属性值 | 对象名->方法名
​
//栈上创建对象访问类中成员
int main(){
    // 创建对象
    Person xiaoming = Person();
    xiaoming.name = "name";
    xiaoming.eat();
    return 0;
}
​
//堆上创建对象访问成员
int main(){
  // 创建对象
  Person* xiao = new Person();
  xiao->name = "name";              //也可以使用*找到堆空间: (*xiao).name = "name";
  xiao->eat;
}

6.3、封装与静态

1)封装:

头文件中定义类,类中定义方法时可以不写具体过程,只进行声明,在对应的cpp文件中书写具体的函数体

/*
 * sqlit.h头文件中的内容
 */
#pragma once
class Person{
public:
    // 定义属性
    std::string name;
    std::string gender;
    int age;
​
    // 定义方法(抽象方法)
    void walk();
    void eat();
};
​
/*
 * sqlit.cpp文件中的内容
 */
#include "iostream"
#include "sqlit.h"
//实现类中没有逻辑的方法
void Person::walk(){
    std::cout << "类中方法的具体实现" ;
}
void Person::eat() {
    std::cout << "具体实现";
}

2)静态:

使用static修饰的成员或属性,static修饰成员的内存是开辟到全局区的与对象无关,在程序编译的时候就已经完成了内存开辟和初始化,并在程序运行的整个过程中都存在,静态属性内存的开辟早与对象,单独存在,并且所有对象共享

注意:类中的静态变量必须在类中定义,类外初始化,定义的静态常量是整形的可以在类外或类内初始化,其它类型与静态变量一样

# include <iostream>
using namespace std;
​
// 定义类放入命名空间中
namespace part1{
    class Person{
        //权限设置为公共的
    public:
        // 定义静态成员属性,类外初始化
        static int countAsy;
        // 定义静态常量,可以在类内进行初始化,也可以在类外进行初始化
        static const int MAIN_X;
        // 定义静态函数
        static void show(){
          cout << "类中的静态函数" << endl;
        }
    };
    // 类外初始化静态成员
    int Person::countAsy = 12;
    const int Person::MAIN_X = 0;
}

访问静态成员的方法:

  • 可以使用对象来访问,不同的对象访问的是同一块空间

  • 可以使用类名直接访问,建议使用类名来访问

int main(){
    // 创建类对象---需要导入命名空间
    Person per;
    // 调用静态变量和常量
    cout << per.MAIN_X << " , " << per.countAsy << endl;
    // 对象调用静态函数
    per.show();
​
    // 直接使用类名进行访问静态成员
    cout << Person::countAsy << endl;
    Person::show();
    return 0;
}

6.4、构造和析构函数

1)构造函数:

构造函数是一个比较特殊的函数,是对类对象进行初始化的函数,用来给对象的属性进行赋值,如果类中不写构造方法,系统会创建一个默认的无参数的构造方法

  • 构造函数的名字必须和类的名字相同

  • 构造函数没有返回值

  • 构造函数可以有不同的重载

// 定义类和构造方法
class Person{
public:
    string name;
    int age;
  
    // 定义无参构造方法
    Person(){
        cout << "Person类的无参构造" << endl;
    }
  
    // 定义有参数的构造方法
    Person(string name){
      this.name = name;
        cout << "有参数的构造方法" << endl;
    }
  
    /*
     * 使用初始化列表创建构造方法
     *  格式:类名(参数):参数1(值1),参数2(值2)···{}
     */ 
    Person(string name,int age):name(name),age(age){
        cout << "使用初始化列表的方法定义构造方法"  << endl;
    }
  
    
    /*
     * 拷贝函数:系统会默认提供
     *          根据一个对象,拷贝出另一个对象,拷贝出来的对象和原来的对象地址不同,但是属性值相同
     */
    Person(const Person& p){        // 参数为常引用类型
         cout << "拷贝构造函数" << endl;
    }
};
​
// 构造函数的使用
int main(){
    // 创建类对象,并使用构造方法进行数据初始化
    Person per = Person();                      // 无参构造
    Person per2 = Person("zhngshan");           // 有参数的构造
    Person per5 = per;                  // 拷贝函数,相当于Person per5 = Person(per)
    Person("xiami")         // 创建匿名对象,特点就是当前行结束,系统就会回收空间就会释放
    return 0;
}

explicit关键字:写在构造函数之前,用来修饰构造函数,表示无法使用隐视调用的方法调用构造方法

2)析构函数:

是对象生命周期的终点,在对象空间被销毁之前调用,在析构函数中一般进行资源的释放和堆内存的销毁

// 格式:以~号开头,后跟类名(){};不能有参数
//      ~类名(){};
​
~Person(){
  cout << "Person析构函数" << endl;
};
​
//示列:
#include <iostream>
// 定义cat类
class cat{};
​
// 定义person类
class Person{
public:
    int age;
    cat* pet;
    // 空参构造
    Person(){
        age = 0;
        pet = new cat();
    }
    // 使用析构函数销毁Person类创建出来时,内部又创建出来的指针指向的新空间
    ~Person(){
        if(pet != nullptr){
            delete pet;
            pet = NULL;
        }
    }
};
​
int main(){
    // 创建person类对象
    Person per = Person();
    return 0;                       // 结束之前会调用析构函数
}

6.5、浅拷贝和深拷贝

如果在堆区创建对象,建议使用深拷贝,如果使用浅拷贝,释放空间时会出现空间的重复释放问题,如果有指针,浅拷贝会拷贝内存地址,深拷贝拷贝新地址

1)浅拷贝:在拷贝构造函数中,直接完成属性的值拷贝操作

#include <iostream>
// 定义cat类
class cat{};
​
// 定义person类
class Person{
public:
    int age;
    cat* pet;
    // 空参构造
    Person(){
        age = 0;
        pet = new cat();
    }
    // 拷贝构造函数
    Person(const Person& p){
        // 默认的拷贝为浅拷贝
        age = p.age;
        pet = p.pet;                //浅拷贝会公用一个内存空间,调用析构函数时会出错
    }
    // 使用析构函数销毁Person类创建出来时,内部又创建出来的指针指向的新空间
    ~Person(){
        if(pet != nullptr){
            delete pet;
            pet = NULL;
        }
    }
};
​
int main(){
    // 创建person类对象
    Person per = Person();
    // 使用拷贝
    Person xiao = per;
    return 0;
}

2)深拷贝:在拷贝构造函数中创造出来新的空间使其属性中的指针指向一个新的空间

// 解决浅拷贝的问题
// 拷贝构造函数
    Person(const Person& p){
        // 默认的拷贝为浅拷贝,将堆区的内容重新拷贝一份,创建一个新地址,内部存放原有地址的内容
        age = p.age;
        pet = new cat(*p.pet);
    }

3)拷贝函数的使用场景:

  • 使用一个已经创建好的对象来初始化一个新对象

  • 值传递的方式给函数参数传参(值传递的方式给函数传参的时候,传的是拷贝的另一份数据,不会改变原有的数据)

  • 值方式返回局部对象

#include <iostream>
using namespace std;
​
class Person{
public:
    Person(){
        cout << "Person类的空参数构造" << endl;
    }
    Person(int age) :age(age){
        cout << "Person类的有参数构造" << endl;
    }
    Person(const Person& p){
        cout << "Person类的拷贝构造函数" << endl;
        age = p.age;
    }
​
private:
    int age;
};
​
// 1、使用一个已经创建好的对象来初始化另一个对象
int main(){
    // 创建一个对象
    Person per = Person(12);
    // 使用拷贝构造函数初始化新对象
    Person per2 = Person(per);
    return 0;
}
​
// 2、值传递的方式给函数传参数
void show(Person p){
  cout << "参数为类对象的全局函数" << endl;
}
int main(){
  // 创建一个类对象,调用show函数
  Person per2 = Persn(12);
  show(per2);
  return 0;
}
​
// 3、值方式返回局部对象
// 值传递的方式返回局部对象
Person doPer(){
    Person pe;                  // 创建的局部对象
    return pe;                  // 调用此方法时,返回的不是pe对象本身,而是拷贝的新对象
}

6.6、常函数和常对象

注意:mutable关键字用来修饰属性,mutable修饰的属性可以在常函数和常对象中进行修改

1)常函数:

使用const修饰类中的成员函数,表示这个类是常函数类,const关键字放在函数的参数列表之后

  • 使用const修饰的函数,在其内部不容许修改属性值

  • 常函数中不容许调用普通函数,只能调用其他的常函数

// 格式:返回值类型 函数名(参数列表) const {};
#include <iostream>
using namespace std;
​
class Person{
private:                    // 定义私有的成员属性
    string name;
    int age;
​
public:
    Person():name(""),age(0){};
    Person(string name,int age):name(name),age(age){};
​
    //定义修改属性值的函数-----不允许调用普通函数和改变属性值
    void changeFunction(string name,int age) const {
        //this->name = name;            
        //this->age = age;
        //show();
    }
    //定义输出属性值的函数
    void show(){
        cout << "name = " << this->name << " , age = " << this->age << endl;
    }
};
​
int main(){
    // 定义类对象
    Person xiaoming = Person("xiaoming",23);
    xiaoming.changeFunction("zhanshan",13);
    xiaoming.show();
    return 0;
}

2)常对象:

创建对象的时候使用const来修饰

  • 常对象可以读取任意属性的数据,但不允许修改

  • 常对象只能调用常函数,不能调用普通的函数

// 格式:
const 类名 对象名 = 类名();
const 类名* 对象名 = new 类名();

6.7、友元(friend)

类的私有成员无法在类的外部进行访问,但有时需要在外部进行访问私有成员,这时可以使用友元,相当于特权函数

1)全局函数做为友元:

#include <iostream>
#include <string>
using namespace std;
​
// 定义房间类
class Home{
    // 将全局函数声明友元函数
    friend void getMyRoom(Home* home);
public:
    string livingRoom = "客厅";
private:
    string myRoom = "卧室";
};
​
// 类外定义一个函数进行访问私有的成员(特权函数)
void getMyRoom(Home* home){
    // 可以直接进行访问public的成员 
    cout << home->livingRoom << endl;
    // 外部直接访问私有成员时要进行友元化,需要将此函数作为友元函数,在类中声明
    cout << home->myRoom << endl;
}

2)成员函数作为友元:

#include <iostream>
#include <string>
using namespace std;
class Home;
class freadMan{
public:
    string name;
    freadMan(string name){
        this->name = name;
    }
    void showHose(Home & p);
};
​
// 定义房间类
class Home{
    // 声明另一个类中的成员函数为友元
    friend void freadMan::showHose(Home &p);
public:
    string livingRoom = "客厅";
private:
    string myRoom = "卧室";
};
​
//实现freadMan类中的成员函数
void freadMan::showHose(Home &p) {
    cout << this->name << "访问房间的:" << p.livingRoom << endl;
    cout << this->name << "访问房间的:" << p.myRoom << endl;
}
int main(){
    // 创建房间对象
    Home ho = Home();
    // 创建朋友对象
    freadMan man = freadMan("zhans");
    man.showHose(ho);
    return 0;
}

3)类做友元

# include <iostream>
# include <string>
using namespace std;
class freadMan;
// 定义房间类
class Home{
    // 声明另一个类为友元
    friend class freadMan;
public:
    string livingRoom;
private:
    string myRoom ;
};
​
// 定义要声明为友元的类
class freadMan{
public:
    Home* home;
    void showHome(){
        home->myRoom = "卧室";
        home->livingRoom = "客厅";
        cout << "访问home类中的公共成员:" << home->livingRoom << endl;
        cout << "访问Home类中的私有成员:" << home->myRoom << endl;
    }
};
int main(){
    // 创建朋友类
    freadMan man;
    man.showHome();
    return 0;
}

6.8、类对象作为类成员

#include <iostream>
#include <string>
using namespace std;
// 定义手机类
class Phone{
public:
    string p_name;
    double p_money;
    // 有参数的构造方法
    Phone(string name,double money){
        cout << "Phone类的有参数构造方法!" << endl;
        this->p_name = name;
        this->p_money = money;
    }
};
​
// 定义人类
class Person{
public:
    string r_name;
    Phone* phone;
    Person(string name,string p_name,double p_money){
        cout << "Person类的有参数构造方法" << endl;
        this->r_name = name;
        this->phone->p_name = p_name;
        this->phone->p_money = p_money;
    }
    void showMan(Person& per);
};
​
void Person::showMan(Person& per) {
    cout << "人名:" << per.r_name <<" ,手机名:" << per.phone->p_name << " ,价格:" << per.phone->p_money << endl;
}
​
int main(){
    Person per = Person("zhasan","华为",45.5);
    per.showMan(per);
    return 0;
}

6.9、运算符重载

运算符重载就是,就是对已有的运算符进行重新的定义,赋予另一种功能,以适应不同的数据类型

1)成员函数进行重载:

/*
 * 成员函数重载+号运算符
 * operator是编译器默认的名称,可以使用简写直接相加
 */
class Person{
public:
    // 声明成员函数重载+号
    Person operator+(Person& p){
        Person temp;
        temp.m_a = this->m_a + p.m_a;
        temp.m_b = this->m_b + p.m_b;
        return temp;
    }
public:
    int m_a;
    int m_b;
};
​
void textPerson(){
    // 创建person对象
    Person p1, p2;
    p1.m_a = 10;
    p1.m_b = 25;
    p2.m_a = 5;
    p2.m_b = 5;
    Person p3 = p1 + p2;                    // 简写
    Person p4 = p1.operator+(p2);           // 正常调用的写法
    cout << p3.m_a << " , " << p3.m_b << endl;
}

2)全局函数进行重载:

/*
 * 全局函数重载
 */
Person operator+(Person& p1,Person& p2){
    Person temp;
    temp.m_a = p1.m_a + p2.m_a;
    temp.m_b = p1.m_b + p2.m_b;
    return temp;
}

3)运算符重载的条件下可以进行函数重载:

Person operator+(Person& p1,Person& p2){
    Person temp;
    temp.m_a = p1.m_a + p2.m_a;
    temp.m_b = p1.m_b + p2.m_b;
    return temp;
}
// 重载
Person operator+(Person& p,int num){
    Person temp;
    temp.m_a = p.m_a + num;
    temp.m_b = p.m_b + num;
    return temp;
}

6.10、继承

1)继承的特点:

  • 所有的成员都可以继承给子类,私有成员可以继承但是子类无法直接进行访问

  • 可以多继承也可以进行单继承,c++中默认使用私有的继承方式

  • 使用继承可以简化代码的复用信,提高代码的拓展性,继承关系是多态的基础

//基础语法:
class 子类名 :继承方式 父类名{};

2)继承方式:

  • public:保留原有的访问权限,父类中的权限是什么,子类就是什么权限

  • private:直接将继承到的函数和方法降为私有的权限

  • protected:超过protected权限的部分,降为proctected权限

3)继承中的构造函:

子类对象在创建时需要先调用父类的构造函数,来初始化父类继承到的部分,默认调用父类的无参数构造,在创建子类对象时,也可以在子类的构造函数后面指定父类的构造函数

//格式:
子类构造函数名():父类构造函数名(参数){};

4)继承中的析构函数:

子类对象在销毁的时候先调用自己的析构函数,在调用父类的析构函数

5)子类和父类中出现同名的成员:

子类会将父类继承到的函数隐藏起来,使用子类的成员,如果需要访问父类中的成员需要显示指定

//格式:
// 调用父类中的同名方法
子类对象.父类名::方法名();
// 调用子类中的同名方法
子类对象.子类名::方法名();

6)多继承:

基本与普通继承没有区别,多个父类之间使用逗号分隔,如果多个父类中存在相同名字的成员,调用时需要指定父类名,否则会出现二义性,报错无法使用。

// 格式:
子类对象.父类名::同名的方法();

7)菱形继承:

两个类继承同一个父类,这两个类又有共同的子类,这样的继承结构就是菱形继承,又叫钻石继承,也就相当于爷父子的关系,当子类继承两个父类时,两个父类又同时继承一个父类,也就是这个子类的爷,这是子类要是使用父类继承下来爷中的变量就需要指定父类的类名,不然会出现二义性

#include <iostream>
using namespace std;
// 定义最高父类
class Anmial {
public:
    int age;
};
// 定义马类,继承父类Anmial
class Horse: public Anmial{
public:
    int a;
};
// 继承Anmial,定义骡子类
class Donkey : public Anmial{
public:
    int b;
};
// 定义子类,继承马类的骡子类
class Mule:public Horse,public Donkey{};
int main(){
    // 创建子类对象
    Mule mu;
    cout << mu.a << endl;
    cout << mu.b << endl;
    //cout << mu.age << endl;  出现二义性
    cout << mu.Donkey::age << endl;         // 解决二义性
    return 0;
}

8)虚继承virtual:

解决菱形继承时命名冲突的问题,使得子类中只保留一份相同的间接父类中的成员

//格式:使用关键字virtual
class 类名 :继承方式 virtual 父类名{};

6.11、多肽

就是使用父类的引用指向子类的对象,如果使用多态想要访问传入的对象的函数,就需要将父类中与子类中相同名字的函数设置为虚函数使用关键字virtual

1)对象转型与成员访问:

/*
 * 对象转型-----多肽的前提条件,对象转型之后,就只能访问父类中的成员,不能访问子类中的成员,
 */
#include <iostream>
using namespace std;
​
// 定义父类
class Animal{
public:
    void show(){
        cout << "Animal类的show函数" << endl;
    }
};
​
// 定义子类,继承父类Animal类
class Dog:public Animal{
 public:
    int age;          // 创建出类的对象如果经过转型,转型之后的对象就无法访问该成员
    void show(){      // 如果子类和父类有同名的函数,子类经过转型之后,使用对象调用函数为父类中的函数,不会调用子类的函数
        cout << "Dog类的show函数" << endl;
    }
};
​
int main(){
    // 父类的引用指向子类的对象
    Dog dog;
    Animal& animal = dog;
    Animal animal_2 = dog;              // 这中写法表示调用Animal类的拷贝构造函数重新拷贝了一份,使用引用公用一块空间
    
    // 父类的指针指向子类的对象
    Dog* dog_2 = new Dog();
    Animal* animal_3 = dog_2;
    return 0;
}

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值