C++高级部分【面向对象篇】

C++高级部分【面向对象篇】,第1张

6.类和对象

给结构添加函数

#include 
struct Date{
    int year;
    int month;
    int day;
    //成员函数说明
    void Display(); 
};

void Date::Display() {
    std::cout<

类与结构

C++定义类和定义结构的方法几乎相同,声明一个类时,使用关键字class

类与结构的唯一区别在成员的默认访问控制权限,默认情况下,结构成员的访问权限是公有的public,而类是私有的private

一般情况下,当只有数据成员没有成员函数时,使用结构,否则使用类。

#include 
class Date{
    int year, month, day;
    public:
        void Display();
};
6.1 类的声明与成员的访问

以日期为例,声明一个类的形式如下

#include 
class Date{
    private:
        int year, month, day;
    public:
        void Display();
        void SetDate(int year, int month, int day);
    protected:
    	// 保护成员声明
};
//类的函数实现在类外面完成
void Date::Display() {
    std::cout<

需要注意的是,类成员在默认情况下都是私有访问属性,所以当私有成员在类内声明时,private关键字可以省略

一般情况下,类的数据成员拒绝类外代码的直接访问,所以常常将数据成员的访问权限设置为私有类型。

在类的成员函数实现中,可以访问私有数据成员,可以对私有成员进行赋值

6.2 成员函数的特征

类中内联成员函数

内联成员函数的声明方式有两种:隐式声明和显示声明

  • 隐式声明的函数定义直接放在类体内。
#include 
class Date{
    private:
        int year, month, day;
    public:
        void Display(){ //内联函数,此时不需要关键字inline,系统默认该成员函数为内联函数
            std::cout<
  • 显示声明的方式

为了保证类声明的一致性,一般把函数的实现放在类体外完成,此时声明内联成员函数,需要采用显示声明的方式,在类体外使用inline关键字,声明的方式如下。

#include 
class Date{
    private:
        int year, month, day;
    public:
        void Display();
        void SetDate(int year, int month, int day);
};
//类的函数实现在类外面完成
inline void Date::Display() {
    std::cout<
6.3成员函数的重载

同一类中的函数重载和普通函数的重载一样,以显示日期为例,将日期按照不同的格式输出

#include 
class Date{
    private:
        int year, month, day;
    public:
        void Display();
        void Display(int y);
    void SetDate(int year, int month, int day);
};
//类的函数实现在类外面完成
void Date::Display() {
    std::cout<
6.4 对象❤️

对象的定义格式

类名 对象名;
//Example
Date birthday;

对象的成员表示

  • 对于公有的成员可以通过.的方式访问

    对象名.公有数据成员名;
    对象名.公有成员函数名(实参列表);``
    
#include 
class Date{
    private:
        int year, month, day;
    public:
        void Display();
        void Display(int y);
        void SetDate(int year, int month, int day);
};
//类的函数实现在类外面完成
void Date::Display() {
    std::cout<
6.5 对象初始化

对象的初始化是通过构造函数完成的。除此之外,c++类还包含析构函数,它的作用是在对象使用结束时,进行一些清理工作。

构造函数与默认构造函数

构造函数的函数名和类名相同,没有返回值,声明和实现的一般形式如下:

class 类名{
    public:
    	类名(参数列表);
};
//构造函数实现
类名::类名(参数列表){
    //函数体
}

以Date对象为例:

#include 
class Date{
    private:
        int year, month, day;
    public:
        Date(int year, int month, int day); //构造函数声明
        void Display();
        void Display(int y);
        void SetDate(int year, int month, int day);
};
//构造函数实现
Date::Date(int year_, int month_, int day_) {
    year = year_;
    month = month_;
    day = day_;
}

int main(){
    Date birthday(1997,9,16);//对象初始化
    birthday.Display();
}

如果一个类中没有构造函数,这时编译系统会在编译时自动生成一个默认形式的构造函数,形式如下

Date::Date(){}

重构构造函数

#include 
class Date{
    private:
        int year, month, day;
    public:
        Date(){}
        //重构构造函数
        Date(int year, int month, int day);
        void Display();
        void Display(int y);
        void SetDate(int year, int month, int day);
};

拷贝构造函数

在创建一个新的对象时,希望新对象和原来的对象中的值完全一致,但是地址不一致,克隆出原来对象的副本,就需要一种特殊的构造函数拷贝构造函数。

拷贝构造函数的参数是本类对象的引用。定义的形式如下:

class 类名{
    public:
    	类名(类名 &对象名);//拷贝构造函数
};
类名::类名(类名 &对象名){ //拷贝构造函数的实现
    //函数体
}

以Date类为例

#include 
class Date{
    private:
        int year, month, day;
    public:
        Date(){} //无参构造函数
        Date(int year, int month, int day); //重构构造函数
        Date(Date &date); //拷贝构造函数的定义
        //成员函数定义
        void Display();
        void Display(int y);
        void SetDate(int year, int month, int day);
};
//拷贝构造函数的实现
Date::Date(Date &x) {
    year = x.year;
    month = x.month;
    day = x.day;
}
Date::Date(int year_, int month_, int day_) {
    year = year_;
    month = month_;
    day = day_;
}
//类的函数实现在类外面完成
void Date::Display() {
    std::cout<

C++程序中,拷贝构造函数在一些情况下会被系统自动调用,总共有三种情况:

  1. 当用类的一个对象区初始化该类的另一个对象时

    int main(){
        Date a(1997,10,20);
        Date b(a);
        b.Display();
    }
    
  2. 当函数的形参是类的对象(不是类指针),调用函数,系统会拷贝该对象的副本,自动调用拷贝构造函数

    void func(Date p){
        p.Display();
    }
    int main(){
        Date a(1997,10,20);
        func(a); //调用该函数时候,系统会拷贝该对象的副本赋值给p
    }
    
  3. 当函数的返回值为对象时

    Date func(){
        Date x(1997,10,20);
        return x;
    }
    int main(){
        Date a;
        a = func();//调用func()函数后返回的对象,赋值给a对象时,系统调用拷贝构造函数
        a.Display();
    }
    

    如果程序没有定义类的拷贝构造函数,系统会自动生成一个默认函数,该函数的功能是把对象中的每个数据成员赋值到新建立的对象中,完成同类对象的克隆。但是,在有些情况下,被克隆的新对象需要个之前的对象有所差别,以日期为例,克隆出的日期要比原来的日期多一天,那么就需要编写拷贝构造函数

    Date::Date(Date &x) {
        year = x.year;
        month = x.month;
        day = x.day+1;
    }
    

析构函数

析构函数用来完成对象被删除前的一些清理工作,也就是专门的扫尾工作。析构函数是在对象的生存期即将结束的时刻由系统自动调用的。

析构函数没有返回值,不接受任何参数。一个类中只能有一个析构函数,一般定义的格式如下。

class 类名{
    public:
    	~类名(); //析构函数的声明
}
//析构函数的实现
类名::~类名(){
    //函数体
}
#include 
int counter = 1;
class Date{
    private:
        int year, month, day;
    public:
        Date(){} //无参构造函数
        Date(Date &date); //拷贝构造函数的定义
        ~Date(); //定义析构函数
};
//拷贝构造函数的实现
Date::Date(Date &date) {
    year = date.year;
    month = date.month;
    day = date.day + 1;
    counter ++;
}
//析构函数的实现
Date::~Date() {
    counter --;
}
6.6 友元

友元是c++提供的语法支持,允许类外的某些特殊的函数或者类直接访问类的私有成员。严格意义上,友元是对数据封装的破坏,但是如果考虑到数据共享的必要性,这种情况是必要的。需要注意的是,使用友元会增加类之间的耦合度,所以尽量少用。

一般情况下,使用关键字friend将别的模块声明为它的友元。

友元函数

#include 
class Date{
    private:
        int year, month, day;
    public:
        Date(int y, int m, int d);
        friend long all_days(Date &a); //friend method statement
};

Date::Date(int y, int m, int d) {
    year = y;
    month = m;
    day = d;
}
// friend method realize
long all_days(Date &a){
    std::cout<

友元类

友元类的一般语法形式为:

class A{
    friend class B;
    //成员变量或成员函数
}
//其中B类的所有成员和方法都可以访问A类的成员和成员函数

以Date类为例:

#include 
class Date{
    private:
        int year, month, day;
    public:
        Date(int y, int m, int d);//construction method
        friend class Clock;//friend class
};

class Clock{
    public:
        void show_now_date(Date &a);
};

void Clock::show_now_date(Date &a) {
    std::cout<

关于友元,需要注意的三点:

  1. 某类的友元,无论是友元函数,还是友元类,都不是该类的成员
  2. 友元关系不能传递,即B是A的友元,C是B的友元,但是C不是A的友元
  3. 友元的关系是单向的
7. 类和对象【高级篇】⭐️

本章介绍静态成员, 常成员,子对象和堆对象,对象数组,对象引用,对象指针

7.1 静态类成员

为了实现一个类的不同对象之间数据和函数的共享,C++引入了静态成员。静态成员包括静态数据成员和静态成员函数。静态成员不属于某个对象,而是属于类,是某个类的所有对象共有的。

静态数据成员

静态成员的定义形式

static 类型 静态变量名;
//Example
static int count;

静态成员的初始化形式

数据类型 类名::静态成员名=值;
//Example
int Student::count=0;

静态成员的访问形式

类名::静态成员名;
//Example
Student::count;

注意:

  1. 私有静态数据成员不能被类体外的函数访问,也不能用对象访问。
  2. 静态数据成员和静态变量一样,是在编译时创建并初始化的,所以在该类的任何对象被创建之前就已经存在
  3. 公有的静态数据成员可以在对象被创建之前通过类名访问,对象创建后,也可以通过对象来访问
  4. 静态数据成员count和sum是所有对象共享的

以学生类为例

#include 

class Student {
private:
    int s_no, score;
    static int count, sum; //static member statement
public:
    Student();
    Student(int, int);
    void print_info();
    void get_avg_info();

};
Student::Student() {
    s_no = 0;
    score = 0;
}

void Student::print_info() {
    std::cout<

关于常成员函数的总结如下:

  1. 常成员函数不能更新对象的数据成员,也不能调用该类中的普通成员函数
  2. 如果将一个对象定义为常对象,则该对象只能调用它的常成员函数,不能调用普通成员函数,常成员函数是常对象的唯一对外接口
7.3 子对象和堆对象

子对象

如果一个对象是另一个类的数据成员,则该对象被称为类的子对象

class B{
    //function body
};
class A{
    B b; //sub class
    //function body
};

需要注意对象初始化的问题,在初始化A对象的时候,也需要子对象初始化。初始化的方法为:

A::A(参数列表):B(参数列表){
	//function body
}

Example

#include 
class A{
    int x, y;
public:
    A(int a, int b){
        x = a;
        y = b;
    }
    void print(){
      std::cout<<"A object: x="<

Output

Lee
Lee_copy
Lee_copy
7.4 对象数组和对象指针

对象数组

对象数组,就是每个数组成员都由某个类的对象构成,对象数组的定义格式为:

类名 对象数组名[数组长度]

通过数组对象访问第i个对象的公有成员

对象数组名[下标].成员名

对象指针

每一个对象初始化后再内存中都会占据一定的空间,对象指针存放的就是对象地址的首地址,声明对象指针的一般形式为:

类名 *对象指针名;
  1. 用对象指针访问单个对象

把它指向一个已声明的对象,然后通过该指针访问对象的公有成员。用指针访问对象的公有成员,用->运算符

  1. 用对象指针访问对象数组
#include 
class Book{
private:
    int count, price;
    static int len;
public:
    Book(int a, int b){
        count = a;
        price = b;
        len ++;
    }
    int get_money(){
        return count * price;
    }
    static int length(){
        return len;
    }

};
int Book::len = 0;
int main() {
    Book book_list[] = {Book(1, 20), Book(2, 30), Book(3, 40)};
    Book *pr = book_list;
    for(int i =0;iget_money()<
7.5 对象引用

当用引用作为函数参数时,其效果与指针一样,传递的是原来变量的地址,所以在函数内部对引用所做的 *** 作,就是对原来变量的 *** 作

在实际应用中,使用对象引用作为函数参数比使用指针更加普遍。

#include 
class Test{
    int x, y;
public:
    Test(){}
    Test(int a, int b){
        x = a;
        y = b;
    }
    void set(int a, int b){
        x = a;
        y = b;
    }
    void print(){
        std::cout<

注意:参数是对象引用,在执行引用调用时,不创建对象副本。当函数执行结束时候,参数的对象不会被撤销,也不调用析构函数

8 继承

以原有类为基础派生处新的类,继承了原有类的特征。

派生新类的过程一般包括继承已有的类的成员、调整已有类成员和添加新的成员。在c++中,一个类可以同时派生多个类,一个类也可以同时继承多个类的特征、即C++是多继承。类的继承方式如下所示

class ClassName: 继承方式1 父类名1, 继承方式2 父类2, ...{
  	// 类的成员声明  
};

C++除了指明继承类,还要指明继承方式。一般继承方式有三种privateprotectedpublic。继承方式规定了如何访问从积累继承的成员,如果不知名继承方式,系统默认继承方式为private

注意,在派生过程中,构造函数和析构函数都不能被继承。

#include 
class Date{
    int year, month, day;
public:
    Date(){}
    ~Date(){}
    void print(){
        std::cout<
8.1 类的继承方式

类的成员访问属性有三种,privateprotectedpublic。类的继承方式也有这三种。

public

父类的公有成员和保护成员的访问属性在子类中不变,但是父类的私有成员在子类中不可直接访问。只能通过继承来的父类接口进行数据访问,其访问属性比子类的私有数据还要高。

#include 
class Date{
private:
    int year;
protected: // 可以被子类访问
    int month, day;

public:
    void set_year(int y){
        year = y;
    }
    int get_year(){
        return year;
    }
    void print(){
        std::cout<

private

继承方式为私有时,父类的公有成员和保护成员都以私有成员存在于子类中。父类的私有成员在子类中不可访问。

经过私有继承后,父类的所有的成员都成为子类的私有成员,如果用这个子类进一步派生其他类,那么其他类无法访问父类的所有成员。因此,私有继承之后,派生类继承来的基类无法在以后的派生类中发挥作用,相当于中止了基类的派生。因此,一般情况下,很少使用私有继承

#include 
class Date{
private:
    int year;
protected: // 可以被子类访问
    int month, day;

public:
    void set_year(int y){
        year = y;
    }
    int get_year(){
        return year;
    }
    void print(){
        std::cout<

protected

保护继承中,父类的公有成员和保护成员都以保护成员出现在子类中,父类的私有成员不可访问。

8.2 子类的构造函数和析构函数

在继承过程中,父类的构造函数和析构函数不能被继承,对新增的成员进行初始化,在子类的构造函数中初始化,对从父类继承下来的成员初始化,可以由父类的构造函数完成。

子类构造函数的一般语法形式为

派生类::派生类名(参数):父类名1(参数1),父类名2(参数2)...
		内嵌对象(参数)...
{
    // 新增成员初始化语句
}
//父类名顺序无关紧要

派生类的构造函数执行的一般顺序是:

  1. 调用基类构造函数,按照类继承的顺序从左到右调用
  2. 调用子类中的内嵌成员的构造函数,按照成员在类的声明顺序依次调用
#include 

class A {
public:
    A(int i) {
        std::cout << "A construction function is called." << i << std::endl;
    }
};

class B {
public:
    B(int i) {
        std::cout << "B construction function is called." << i << std::endl;
    }
};

class C{
public:
    C(int i) {
        std::cout << "C construction function is called." << i << std::endl;
    }
};

class D : public B, public A, public C {
private:
    A number_a; //内嵌对象
    C number_c;
    B number_b;
    int d;
public:
    D(int a, int b, int c, int d) : A(a), B(b), C(c),
                                    number_a(a), number_b(b), number_c(c) {
        std::cout << "D construction function is called." << std::endl;
        this->d = d;
    }
};

int main() {
    D d(1, 2, 3, 4);
}

析构函数

析构函数的调用和构造函数调用的顺序相反

#include 

class A {
public:
    A(int i) {
        std::cout << "A construction function is called." << i << std::endl;
    }
    ~A(){
        std::cout<<"A is deconstruct"<d = d;
    }
    ~D(){
        std::cout<<"D is deconstruct"<
8.3 多继承中的二义性问题

对于多继承的情况,如果父类拥有多个相同的成员或者函数,同时子类也需要有同名的函数,这个时候就必须通过::来标识成员。

#include 

class A {
public:
    int v;
    void func(){
        std::cout<<"class A, member v="<

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/797816.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-06
下一篇2022-05-06

发表评论

登录后才能评论

评论列表(0条)