必看!用示例代码学C++类与对象,快速掌握基础知识,高效提升编程能力

C++类与对象


目录

C++类与对象

●1.类的引入

●2.类的访问限定符及其封装

●3.类的作用域及实例化

●4.类对象模型

●5.this指针

●6.类的6个默认成员函数

●7.类的构造函数

●8.类的析构函数

●9.拷贝构造函数

●10.赋值运算符重载

●11.const成员

●12.再谈构造函数

●13.static静态成员

●14.友元

●15.内部类


1.类的引入

C语言结构体中只能定义变量,在C++ 中,结构体内不仅可以定义变量,也可以定义函数。比如:之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数。

示例1:struct来实现栈

c++ 复制代码
#include <iostream>
using namespace std;
typedef int DataType;
struct stack
{
    DataType *array;
    DataType stack_cap;
    DataType size;
    void init_stack(size_t cap)
    {
        array = (DataType *)malloc(sizeof(DataType) * cap);
        if (array == nullptr)
        {
            perror("error");
            exit(0);
        }
        stack_cap = cap;
        size = 0;
    }
    bool push(DataType x)
    {
        if (size == stack_cap)
        {
            cout << "栈满!" << endl;
            return false;
        }
        array[size] = x;
        size++;
    }
    bool pop()
    {
        if (size == 0)
        {
            cout << "栈空!" << endl;
            return false;
        }
        cout << array[size] << endl;
        size--;
    }
    int top()
    {
        if (size == 0)
        {
            cout << "栈空!" << endl;
            return false;
        }
        return array[size];
    }
    void destory()
    {
        if (array)
        {
            free(array);
            array = nullptr;
            stack_cap = 0;
            size = 0;
        }
    }
} ;
int main()
{
    stack s;
    s.init_stack(5);
    s.push(1);
    s.push(2);
    s.push(3);
    s.push(4);
    cout << s.top() << endl;
    s.pop();
    cout << s.top() << endl;
    s.destory();
    return 0;
}

示例2:class来实现栈

c++ 复制代码
#include <iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
typedef int DataType;
//第一种声明与定义法:(适用于平时学习与算法练习)
class stack
{
public:
    void init_stack(size_t len)
    {
        array = new int(len);
        if (!array)
        {
            perror("开辟失败!");
            exit(0);
        }
        stack_cap = len;
        size = 0;
    }
    bool push(DataType x)
    {
        if (size == stack_cap)
        {
            cout << "栈满!" << endl;
            return false;
        }
        array[size] = x;
        size++;
    }
    bool pop()
    {
        if (size == 0)
        {
            cout << "栈空!" << endl;
            return false;
        }
        cout << array[size] << endl;
        size--;
    }
    int top()
    {
        if (size == 0)
        {
            cout << "栈空!" << endl;
            return false;
        }
        return array[size];
    }
    void destory()
    {
        if (array)
        {
            delete array);
            stack_cap = 0;
            size = 0;
        }
    }
private:
    DataType *array;
    DataType stack_cap;
    DataType size;
};
//第二种声明与定义法:(适用于软件项目开发的代码规范)(头源分离)
// my_Stack.h(头)
class stack
{
public:
    void init_stack(size_t len);
    bool push(DataType x);
    bool pop();
    int top();
    void destory();
private:
    DataType *array;
    DataType stack_cap;
    DataType size;
};
//my_Stack.cpp(源)
void stack::init_stack(size_t len)
{
    array = new int(len);
    if (!array)
    {
        perror("开辟失败!");
        exit(0);
    }
    stack_cap = len;
    size = 0;
}
bool stack::push(DataType x)
{
    if (size == stack_cap)
    {
        cout << "栈满!" << endl;
        return false;
    }
    array[size] = x;
    size++;
}
bool stack::pop()
{
    if (size == 0)
    {
        cout << "栈空!" << endl;
        return false;
    }
    cout << array[size] << endl;
    size--;
}
int stack::top()
{
    if (size == 0)
    {
        cout << "栈空!" << endl;
        return false;
    }
    return array[size];
}
void stack::destory()
{
    if (array)
    {
        delete (array);
        stack_cap = 0;
        size = 0;
    }
}
int main()
{
    stack s;
    s.init_stack(5);
    s.push(1);
    s.push(2);
    s.push(3);
    cout<<s.top()<<endl;
    s.pop();
    cout<<s.top()<<endl;
    s.destory();
    return 0;
}

2.类的访问限定符及其封装

访问限定符说明:

  1. public修饰的成员在类外可以直接被访问,在类内也可以直接被访问;而protected和private修饰的成员在类外不能被访问,在类内可以被访问。
  2. class的默认访问权限为private,struct为public(因为struct要兼容C)。
  3. 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

3.类的作用域及实例化

示例:

c++ 复制代码
class student_info
{
public:
    void set_stuinfo(string name,string gender,int age);
    void show_stuinfo();
private:
    string _name ;
    string _gender;
    int _age;
};
void student_info::set_stuinfo(string name,string gender,int age)
{
    _name=name;
    _gender=gender;
    _age=age;
}
void student_info::show_stuinfo()
{
    cout << _name << " " << _gender << " " << _age << endl;
}
int main()
{
    student_info si;
    si.set_stuinfo("zhangzhichao", "男",23);
    si.show_stuinfo();
    return 0;
}

4.类对象模型

结论:一个类的大小,实际就是该类中"成员变量"之和,当然要注意内存对齐;注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

5.this指针

this指针的特性:

c++ 复制代码
void display()
    {cout<<date<<endl;}
void display(Data *this)
    {cout<<this->date<<endl;}
//编译器处理成员函数隐含的this指针
 
typedef int dataType;
class Date
{
public:
    void init_date(dataType y, dataType m, dataType d)
    {
        _year = y;  //等价 this->_year=y;
        _month = m; //等价 this->_month=m;
        _day = d;   //等价 this->_day=d;
    }
 
    void show_data()
    {
        cout << _year << " " << _month << " " << _day << endl;
        //等价 cout << this->_year << " " << this->_month << " " << this->_day << endl;
    }
 
private:
    dataType _year;
    dataType _month;
    dataType _day;
};
  1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值;
  2. this 指针是 " 成员函数 " 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传 递,不需要用户传递;

6.类的6个默认成员函数

  1. 初始化和清理:构造函数主要完成初始化工作,析构函数主要完成清理工作;(常用)
  2. 拷贝复制:拷贝构造是使用同类对象初始化创建对象,赋值重载主要是把一个对象赋值给另一个对象;(常用)
  3. 取地址重载:主要是普通对象和const对象取地址,这两个很少会去自己实现;

7.类的构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

示例1:

c++ 复制代码
typedef int dataType;
class Date
{
public:
    // void init_date(dataType y, dataType m, dataType d)
    // {
    //     _year = y;  // this->_year=y;
    //     _month = m; // this->_month=m;
    //     _day = d;   // this->_day=d;
    // }
    // 用构造函数取代上述初始化函数
    //需要注意的是:构造函数可以重载
      Date(){}//无参构造
    Date(dataType y, dataType m, dataType d)
    {
        _year = y;
        _month = m;
        _day = d;  
    }//有参构造
 
    void show_date()
    {
        cout << _year << " " << _month << " " << _day << endl;
        // cout << this->_year << " " << this->_month << " " << this->_day << endl;
    }
private:
    dataType _year;
    dataType _month;
    dataType _day;
};
 
int main()
{
    Date d;//无参构造
    Date d1(2024,12,21);//有参构造
    d1.show_date();
    Date d2(2025,1,1);//有参构造
    d2.show_date();
    return 0;
}

如果类中没有显示定义出构造函数,则C++ 编译器将会自动生成一个无参的默认构造函数,一旦用户显式定义了编译器将不会再默认生成。

C++ 把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

示例2:

c++ 复制代码
typedef int TimeType;
typedef int DataType;
class TIME{
public:
    TIME()
    {
        cout<<"TIME()"<<endl;
        _hour=0;
        _minute=0;
        _seconds=0;
    }
public:
    TimeType _hour;
    TimeType _minute;
    TimeType _seconds;
};
class DATE{
public:
    void showdata()
    {
        cout<<_year<<" "<<_month<<" "<<_day<<endl;
        cout<<t._hour<<" "<<t._minute<<" "<<t._seconds<<endl;
        //0 0 0
        //0 0 0
    }
private:
    //内置类型
    DataType _year=0;
    DataType _month=0;
    DataType _day=0;
    //内置类型成员变量在类中声明时可以给默认值
 
    //自定义类型
    TIME t;
};
int main()
{
    DATE d;
    d.showdata();
    return 0;
}

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

示例3:

c++ 复制代码
typedef int dataType;
class Date
{
public:
    Date()
    //无参构造函数
    {
        _year=2024;
        _month=12;
        _day=31;
    }
    Date(dataType y=2024, dataType m=12, dataType d=31)
    //全缺省构造函数
    {
        _year = y;
        _month = m;
        _day = d;  
    }
private:
    dataType _year;
    dataType _month;
    dataType _day;
};

8.类的析构函数

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。(一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。)

示例1:以栈为例

c++ 复制代码
typedef int DataType;
class stack
{
public:
    stack()
    {
        cout << "stack()构造函数" << endl;
        // 默认开辟
        arr = new int(10);
        if (arr == nullptr)
        {
            perror("开辟失败!");
            exit(0);
        }
        stack_cap = 10;
        size = 0;
    }
 
 
    ~stack()
    {
        cout << "~stack()析构函数" << endl;
        if (arr)
        {
            delete[] arr;
            arr = nullptr;
            stack_cap = 0;
            size = 0;
        }
    }
 
private:
    DataType *arr;
    DataType stack_cap;
    DataType size;
};

示例2:

c++ 复制代码
typedef int TimeType;
typedef int DataType;
class TIME
{
public:
    TIME()
    {
        cout << "TIME()构造函数" << endl;//1
    }
    ~TIME()
    {
        cout << "~TIME()析构函数" << endl;//4
    }
 
public:
    TimeType _hour;
    TimeType _minute;
    TimeType _seconds;
};
class DATE
{
public:
    DATE()
    {
        cout << "DATE()构造函数" << endl; //2
    }
    //而对于初始化对象,虽然只在main函数中定义date的对象,但是先构造该类的对象调用该类的构造函数,在调用该类的构造函数;
    ~DATE()
    {
        cout << "~DATE()析构函数" << endl;//3
    }
    //但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函
    //数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁;
    //所以最终的销毁顺序先是date的析构,再是time的析构;
 
    //注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
private:
    // 内置类型
    DataType _year;
    DataType _month;
    DataType _day;
    // 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;
    // 自定义类型
    TIME t;
    //而t是TIME类对象,所以在d销毁时,要将其内部包含的TIME类的t对象销毁,所以要调用TIME类的析构函数;
};
int main()
{
    DATE d;
    return 0;
}

注意:如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

示例3:用两个栈实现队列

c++ 复制代码
#include <unistd.h>
#include <stack>
template <class T>
// namespace my_Queue
// {
class my_Queue
{
public:
    void push(T x)
    {
        push_s.push(x);
    }
 
    void pop()
    {
        if(push_s.size()==0&&pop_s.size()==0)
        {
            cout<<"队列中无数据元素,无需出队!"<<endl;
            exit(0);
        }
        if (pop_s.empty())
        {
            while (!push_s.empty())
            {
                T tmp = push_s.top();
                pop_s.push(tmp);
                push_s.pop();
            }
            cout << "队首元素:" << pop_s.top() << endl;
            pop_s.pop();
        }
        else
        {
            cout << "队首元素:" << pop_s.top() << endl;
            pop_s.pop();
        }
    }
 
private:
    stack<T> push_s;
    stack<T> pop_s;
};
// }
void memu()
{
    cout << "################" << endl;
    cout << "####1. 入队#####" << endl;
    cout << "####2. 出队#####" << endl;
    cout << "################" << endl;
}
int main()
{
    my_Queue<int> mq;
    int choice;
    while (1)
    {
        memu();
        cout << "请输入功能:";
        cin >> choice;
        switch (choice)
        {
        case 1:
            cout << "请输入入队元素:";
            int input;
            cin >> input;
            mq.push(input);
            break;
        case 2:
            mq.pop();
            break;
 
        default:
            break;
        }
        sleep(3);
        system("clear");
    }
    return 0;
}

9.拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。拷贝构造函数是构造函数的一种重载形式,拷贝构造函数的参数必须只有一个且必须是类类型对象的引用。

示例1:

c++ 复制代码
typedef int dataType;
class Date
{
public:
    Date(dataType y = 2024, dataType m = 12, dataType d = 31)
    {
        _year = y;
        _month = m;
        _day = d;
    }
 
    // Date(const Date d) 错误写法,将会发生无限递归调用
    Date(const Date &d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
 
 
    void showDate()
    {
        cout << _year << " " << _month << " " << _day << endl;
    }
 
private:
    dataType _year;
    dataType _month;
    dataType _day;
};
 
int main()
{
    Date d1;
    Date d2(d1); //调用拷贝构造函数
    d2.showDate();//2024 12 31
    return 0;
}

若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

浅拷贝可以解决一类相对简单的问题,比如日期的代码示例;但是对于栈的代码示例通过浅拷贝是无法去实现的,需要通过深拷贝去解决。注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

示例2:拷贝构造函数典型调用场景

  1. 使用已存在的对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象
c++ 复制代码
typedef int dataType;
class Date
{
public:
    Date(dataType y, dataType m, dataType d)
    {
        _year = y;
        _month = m;
        _day = d;
    }
    Date(const Date &d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    void showDate()
    {
        cout << _year << " " << _month << " " << _day << endl;
    }
private:
    dataType _year;
    dataType _month;
    dataType _day;
};
Date Test(Date d)
{
    Date temp(d);
    return temp;
}
int main()
{
    Date d1(2024,12,31);
    Date d2(d1);
    d2.showDate();
    //2024 12 31
    Test(d1).showDate();
    //2024 12 31
    return 0;
}

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

10.赋值运算符重载

  1. 运算符重载

示例:类外重载

c++ 复制代码
typedef int dataType;
class Date
{
public:
    Date(dataType y=2024, dataType m=12, dataType d=29)
    {
        _year = y;
        _month = m;
        _day = d;
    }
//private:
    dataType _year;
    dataType _month;
    dataType _day;
};
//类外重载 (需要将类内private注释,私有成员变量类外不能访问)
//因为这种情况下不可再使用private:所以会使其封装性出现问题,所以后期我们将通过友元来实现封装性
bool operator==(const Date &d1,const Date &d2)
{
    return d1._year==d2._year&&
            d1._month==d2._month&&
              d1._day==d2._day;
}
void test1()
{
    Date d1(2024,12,31);
    Date d2(2024,12,30);
    bool ret=(d1==d2);
    cout<<ret<<endl;
}

示例:类内重载

c++ 复制代码
typedef int dataType;
class Date
{
public:
    Date(dataType y = 2024, dataType m = 12, dataType d = 29)
    {
        _year = y;
        _month = m;
        _day = d;
    }
 
    //类内重载
    // bool operator==(Date* this,const Date &d)
    // 需要注意的是,左操作数是this,指向调用函数的对象
    bool operator==(const Date &d)
    {
        return this->_month == d._year &&
               this->_year == d._year &&
               this->_day == d._day;
    }
 
private:
    dataType _year;
    dataType _month;
    dataType _day;
};
void test2()
{
    Date d1(2024, 12, 31);
    Date d2(2024, 12, 30);
    bool ret = (d1.operator==(d2));
    cout << ret << endl;
}
  1. 赋值运算符重载

示例:

c++ 复制代码
typedef int dataType;
class Date
{
public:
    //构造函数
    Date(dataType y, dataType m, dataType d)
    {
        _year = y;
        _month = m;
        _day = d;
    }
 
    //拷贝构造
    Date(const Date& d)
    {
        _year=d._year;
        _month=d._month;
        _day=d._day;
    }
   
    //赋值运算符重载
    Date& operator=(const Date &d)
    {
        if(this!=&d)
        {
            this->_year=d._year;
            this->_month=d._month;
            this->_day=d._day;
        }
        return *this;
    }
 
    void showdate()
    {
        cout<<_year<<" "<<_month<<" "<<_day<<endl;
    }
 
private:
    dataType _year;
    dataType _month;
    dataType _day;
};
void test3()
{
    Date d1(2024,12,31);
    d1.showdate();
    Date d2(d1);
    d2.showdate();
    Date d3=d1;
    d3.showdate();
}

注意:赋值运算符只能重载类内的成员函数不能重载全局函数,赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数。

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

示例:

c++ 复制代码
typedef int datetype;
typedef int timetype;
class Time
{
public:
    Time &operator=(const Time &t)
    {
        if (this != &t)
        {
            this->_hours = t._hours;
            this->_minutes = t._minutes;
            this->_seconds = t._seconds;
        }
        return *this;
    }
 
private:
    timetype _hours;
    timetype _minutes;
    timetype _seconds;
};
 
class Date
{
public:
    Date(dataType y, dataType m, dataType d)
    {
        _year = y;
        _month = m;
        _day = d;
    }
 
private:
    // 内置类型
    dateType _year;
    dateType _month;
    dateType _day;
    // 自定义类型
    Time t;
};
void test4()
{
    Date d1(2024,12,31);
    Date d2;
    d2 = d1;  //内置类型直接赋值,自定义类型赋值重载
    //2024 12 31
}

编译器生成的默认赋值运算符重载函数可以完成字节序的值拷贝(浅拷贝),比如上述代码中所属展示的内置类型的值拷贝;而对于一些问题,只可以使用深拷贝去解决(比如栈问题);

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

  1. 前置加加和后置加加重载

示例:

c++ 复制代码
typedef int datetype;
class Date
{
public:
    Date(){}
    Date(datetype y,datetype m, datetype d)
    {
        _year = y;
        _month = m;
        _day = d;
    }
 
    //后置++
    //注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
    Date &operator++()
    {
        _day += 1;
        return *this;
    }
 
    //前置++
    //C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
    //注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1
    //而temp是临时对象,因此只能以值的方式返回,不能返回引用
    Date operator++(int)
    {
        Date temp(*this);
        _day+=1;
        return temp;
    }
 
    void showdate()
    {
        cout << _year << " " << _month << " " << _day << endl;
    }
 
private:
    datetype _year;
    datetype _month;
    datetype _day;
};
void test5()
{
    Date d;
    Date d1(2024, 12, 29);
    d=d1;   //默认赋值运算符重载
    d.showdate(); //2024 12 29
    d++;
    d.showdate();//2024 12 30
    ++d;
    d.showdate();//2024 12 31
}

11.const成员

将const修饰的"成员函数"称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

示例:

c++ 复制代码
typedef int datetype;
class Date
{
public:
    void showdate() const
    //等价于:void showdate(const Date* this)
    {
        //_year = 2023; error
        // test.cpp: In member function 'void Date::showdate() const':
        // test.cpp:1122:15: error: assignment of member 'Date::_year' in read-only object
        //          _year = 2023;
        cout << _year << " " << _month << " " << _day << endl;
        //等价于:cout << this->_year << " " << this->_month << " " << this->_day << endl;
    }
 
private:
    datetype _year = 2024;
    datetype _month = 12;
    datetype _day = 31;
};

测试用例代码:

请思考下面的几个问题:

  1. const对象可以调用非const成员函数吗? 不可以
  2. 非const对象可以调用const成员函数吗? 不可以
  3. const成员函数内可以调用其它的非const成员函数吗?不可以
  4. 非const成员函数内可以调用其它的const成员函数吗?可以
c++ 复制代码
class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void func1(){}
    void func2() const{}
    void Print()
    {
        func2();
        cout<<"void func2() const"<<endl;
        //正常输出:void func2() const
    }
    void Print() const
    {
        func1();
        cout<<"void func1()"<<endl;
        // test.cpp: In member function 'void Date::Print() const':
        // test.cpp:1159:15: error: passing 'const Date' as 'this' argument of 'void Date::func1()' discards qualifiers [-fpermissive]
        //          func1();
    }
    
private:
    int _year;  
    int _month;
    int _day;   
};
void Test()
{
    Date d1(2022, 1, 13);
    d1.Print();
    const Date d2(2022, 1, 13);
    d2.Print();
}

12.再谈构造函数

  1. 构造函数体赋值

示例:

c++ 复制代码
typedef int datetype;
class Date
{
public:
    Date(datetype year, datetype month, datetype day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
 
private:
    datetype _year;
    datetype _month;
    datetype _day;
};
void test()
{
    Date d1(2024, 12, 31);
}

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

  1. 初始化列表

示例:

c++ 复制代码
typedef int datetype;
class Date
{
public:
    Date(datetype year, datetype month, datetype day)
        : _year(year), _month(month), _day(day)
        //每个成员变量在初始化列表中只能出现一次;
    {
    }
 
private:
    datetype _year;
    datetype _month;
    datetype _day;
};
void test1()
{
    Date d1(2024, 12, 31);
}  

类中包含以下成员,必须放在初始化列表位置进行初始化:引用成员变量;const成员变量;自定义类型成员变量(该类中没有默认构造函数);

示例:

c++ 复制代码
class A{
public:
    A(int a)
    : _a(a)
    {}
private:
    int _a;  
};
class B{
public:
    B(int ref,int a)
    :_ref(ref),_n(10),_a(a)
    {}
private:
    int &_ref;//引用成员变量
    const int _n;//const成员变量
    A _a;//自定义类型成员变量(该类中没有默认构造函数)
};  
void test2()
{
    B b(10,10);
}

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

c++ 复制代码
class A{
public:
    A(int a=0)
    : _a(a)
    {
        cout<<"构造函数A(int a=0)"<<endl;
    }
private:
    int _a;  
};
class B{
public:
    B(int b)
    :_b(b)
    {}
private:
    A _a;//默认使用
    int _b;
};  
void test3()
{
    B b(10);
    //输出结果:构造函数A(int a=0)
}

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

c++ 复制代码
class B
{
public:
    B(int a, int b)
        : _b(b),
          _a(a)
        //即使这里初始化顺序为先_b,后_a,但实际的初始化顺序仍为先_a,后_b
    {
    }
 
private:
    //函数声明次序为先_a,后_b
    int _a;
    int _b;
};
  1. explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。用explicit修饰构造函数,将会禁止构造函数的隐式转换。

测试代码示例:

c++ 复制代码
class Date
{
public:
    // 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
    // explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
    explicit Date(int year)
        : _year(year)
    {
    }
    /*
    // 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具
   有类型转换作用
    // explicit修饰构造函数,禁止类型转换
    explicit Date(int year, int month = 1, int day = 1)
    : _year(year)
    , _month(month)
    , _day(day)
    {}
    */
    Date &operator=(const Date &d)
    {
        if (this != &d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
        return *this;
    }
 
private:
    int _year;
    int _month;
    int _day;
};
void Test()
{
    Date d1(2);
    // 用一个整形变量给日期类型对象赋值
    // 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
    d1 = 2023;
    // 将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用
}

13.static静态成员

  1. 声明为static的类成员称为类的静态成员;
  2. 用static修饰的成员变量,称之为静态成员变量;静态成员变量一定要在类外进行初始化;
  3. 用static修饰的成员函数,称之为静态成员函数;

特性示例:

c++ 复制代码
// 1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
static int cnt = 0;
class A
{
public:
    void increase_cnta() { cnt++; }
};
class B
{
public:
    void increase_cntb() { cnt++; }
    void funcb()
    {
        cout << _b << endl;
        _b++;
        cout << _b << endl;
    }
 
    static int get_b()
    {
        // 4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员
        //this->_bbb = 1; error
        // test.cpp: In static member function 'static int B::get_b()':
        // test.cpp:1253:9: error: 'this' is unavailable for static member functions
        //      this->_bbb=1;
        return _bb;
    }
 
//5.静态成员也是类的成员,受public、protected、private 访问限定符的限制
public:
    // 2.类静态成员变量声明,类中只是声明
    static int _b;
 
private:
    static int _bb;
    int _bbb;
};
// 2.静态成员变量必须在类外定义,定义时不添加static关键字
// 3.类静态成员即可用 类名::静态成员 或者对象.静态成员来访问
int B::_b = 0;
int B::_bb=0;
int main()
{
    A a;
    a.increase_cnta();
    B b;
    b.increase_cntb();
    cout << cnt << endl;
    // 输出结果:2
 
    b.funcb();
    cout << b._b << endl;
    // 输出结果:0 1 1
 
    cout<<b.get_b()<<endl;
    // 输出结果:0
    return 0;
}

14.友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。友元分为:友元函数和友元类。

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

示例代码1:友元函数

c++ 复制代码
class Date
{
    friend ostream &operator<<(ostream &_cout, const Date &d);
    friend istream &operator>>(istream &_cin, Date &d);
 
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {
    }
 
private:
    int _year;
    int _month;
    int _day;
};
ostream &operator<<(ostream &_cout, const Date &d)
{
    _cout << d._year << "-" << d._month << "-" << d._day;
    return _cout;
}
istream &operator>>(istream &_cin, Date &d)
{
    _cin >> d._year;
    _cin >> d._month;
    _cin >> d._day;
    return _cin;
}
int main()
{
    Date d;
    cout<<"请输入年月日:";cin >> d; //对cin输入进行重载
    cout<<"年月日为:";cout << d << endl;//对cout输出进行重载
    return 0;
}

注意:

  1. 友元函数可以访问类的私有和保护成员变量,但是它不是类的成员函数;
  2. 友元函数不能用const修饰;
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符的限制;
  4. 一个函数可以是多个类的友元函数,因为一个函数可能用到多个类内部的私有成员变量;
  5. 友元函数调用与普通函数调用原理相同;

示例代码2:友元类

c++ 复制代码
class Time
{
    friend class Date;
public:
    Time(int hour=23,int minute=59,int second=59)
        :_hour(hour),_minute(minute),_second(second)
    {
    }
 
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
public:
    Date(int year = 2024, int month = 12, int day = 31)
        : _year(year), _month(month), _day(day)
    {
    }
 
    void show_DateTime()
    {
        cout<<"年月日: "<<_year<<":"<<_month<<":"<<_day<<endl;
        cout<<"时分秒: "<<_t._hour<<":"<<_t._minute<<":"<<_t._second<<endl;
    }
 
private:
    int _year;
    int _month;
    int _day;
    Time _t;
};
int main()
{
    Date d;
    d.show_DateTime();
    return 0;
}

15.内部类

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

示例:

c++ 复制代码
class A{
public:
    class B{
    public:
        void show(const A& a)
        {
            cout<<k<<endl;//注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
            cout<<a.h<<endl;
        }
    };
private:
    int h=1;
    static int k;
};
int A::k=0;
int main()
{
    A::B b;
    b.show(A()); //A()为匿名对象
    //sizeof(外部类)=外部类,和内部类没有任何关系。
    return 0;
}

注意:

  1. 内部类可以定义在外部类的public、protected、private都是可以的;
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名;
  3. sizeof(外部类)=外部类,和内部类没有任何关系;

<您的三连和关注是我最大的动力>
🚀 文章作者:张同学的IT技术日记
分类专栏:C++系列

相关推荐
ZHOUZAIHUI35 分钟前
WSL(Ubuntu24.04) 安装PostgreSQL
开发语言·后端·scala
i02081 小时前
SpringBoot 项目配置
java·spring boot·后端
月屯2 小时前
后端go完成文档分享链接功能
开发语言·后端·golang
Franciz小测测2 小时前
Python连接RabbitMQ三大方案全解析
开发语言·后端·ruby
海梨花2 小时前
又是秒杀又是高并发,你的接口真的扛得住吗?
java·后端·jmeter
Livingbody2 小时前
win11上wsl本地安装版本ubuntu25.10
后端
用户8356290780513 小时前
如何在 C# 中自动化生成 PDF 表格
后端·c#
星释3 小时前
Rust 练习册 44:Trait 中的同名函数调用
开发语言·后端·rust
京东零售技术3 小时前
并发丢数据深度剖析:JED的锁机制与事务实战踩坑及解决方案
后端
f***68603 小时前
问题:Flask应用中的用户会话(Session)管理失效
后端·python·flask