类与对象详解(C++)(近2w字梳理全部知识点)

面向过程与面向对象初了解

C语言是面向过程的,关注过程,分析出求解问题的步骤,通过函数调用逐步解决
C++是基于面向对象的,关注的是对象,讲一件事情拆分成不同的对象,靠对象之间的交互完成
拿一个简单的外卖系统举例子:
面向过程:关注实现下单、接单、送餐这些过程;在代码层面就是–方法、函数
面向对象:关注实现类对象和对象之间的关系;用户、商家、骑手以及他们之间的关系;在代码层面就是类的设计及其类之间的关系
C++基于面向对象:面向对象与过程混编
原因在于C++兼容C
Java只有面向对象

类的引入

C语言中,结构体中只能定义变量
C++中,结构体内部不仅可以定义变量,也可以定义函数
而在C++中,更喜欢用class去代替struct
例如定义一个学生类:

struct Student
{
	char name[10];
	int  age;
	int  id;
};
int main()
{
	struct Student s1; // 兼容C的语法
	Student S2; // 在C++中,Student是类名,也是类型
}

在C++中兼容C语言中结构体的用法,同时struct在C++中也升级为了类
C++类与结构体的区别在于除了定义变量也可以定义函数(方法)
类中的数据称为类的成员:类中的数据称为类的属性或者成员变量,类中的函数称为类的方法或者成员函数

类的两种定义方法:
1、声明与定义全部在类体中(如果成员函数在类中定义,编译器会将其当成内联函数处理)

class Person
{
public: // 下面讲解此处public及后面private的含义
    void showInfo() // 成员函数存储在公共的代码段中
    {
        cout << _name << " " << _sex << endl;
    }

private:
    char *_name[10];
    char *_sex[10];
};

2、声明放在.h文件中,类的定义放在.cpp文件中

// person.h放声明
class Person
{
public:
	void ShowInfo();
private:
	char *_name[10];
	char *_sex[10];
};
// person.cpp放类的实现:
#include "person.h"
void Person::ShowInfo()
{
	cout << _name << " " << _sex << endl;
}

在正式的项目工程中,一般采用第二种方式

类的访问限定符及封装

访问限定符: C++实现封装的方式:用类将对象的属性和方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部用户使用
访问限定符有3种:public(共有),protected(保护),private(私有)
public:类内可以访问,类外也可以进行访问
private:类内可以访问,类外不能进行访问
protected在之后进行介绍,此处可以先忽略
[访问限定符说明]:
1、public修饰的成员在类外可以直接被访问
2、protected与private修饰的成员在类外不能直接被访问(在此处private与protected是类似的,在之后的章节说明此处两个的区别)
3、访问权限作用域从该访问限定符开始出现的位置开始到下一个访问限定符为止
4、class的默认访问权限为private(私有),struct为public(因为要兼容c)
注意:访问限定符只有在编译时才有用,当数据映射到内存后,没有任何访问限定符上的区别

将成员属性设置为私有的好处:
1、可以自己控制读写权限
2、对于写权限,我们可以检查数据的有效性

class T1
{
	int _A; // 默认权限:是私有成员
};
struct T2
{
	int _B; // 默认权限:是公有成员
};
int main()
{
	T1 t1;
	t1._A = 100; // 报错
	T2 t2;
	t2._B = 100; // 成功
}

[C++中struct与class的区别是什么?]
1、C++需要兼容C,因此C++中struct可以当成结构体去使用,另外C++中struct可以和class一样用来定义类
2、struct成员默认访问方式是public,class成员默认访问方式是private

[封装]:
面向对象的三大特性:封装、继承、多态
在类与对象阶段,这里值研究类的封装特性,那什么是封装?
封装: 将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节
封装的本质上也是一种管理,我们使用类数据和方法都进行封装,使用private/protected把成员封装起来,开放一些共有的成员函数对成员合理的访问,所以封装的本质是一种管理

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中
在类体外使用成员,需要用::指明成员属于哪一个类域

class Person
{
public:
    void PrintPersonInfo();

private:
    char _name[20];
    char _gender[3];
    int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout<<_name<<" "_gender<<" "<<_age<<endl; 
}

类的实例化

用类类型去创建对象的过程,称为类的实例化
1、类致使一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间(相当于一个房子的图纸,并没有实际的房子)
2、一个类可以实例化出多个对象,实例化出的对象,占用实际的物理空间,存储类成员变量

类对象模型

如何计算对象的大小?
计算类或者类对象的大小,只看类里面成员变量,要考虑内存对齐,C++的内存对齐与C语言的内存对齐一致

class A1
{
public:
    void f1() {}

private:
    int _a;
};
// A1大小为4字节
// 类中仅有成员函数
class A2
{
public:
    void f2() {}
};
// A2的大小为1字节
// 类中什么都没有---空类
class A3
{
};
// A3的大小为1字节

this指针

this指针指向被调用成员函数所属的对象
this指针存储在栈中(形参),有些编译器会将其放在寄存器中(vs放在ecx寄存器中)
this指针默认是左操作数
在这里插入图片描述

类的6个默认成员函数

如果一个类中什么成员都没有,称为空类,空类中是什么都没有嘛?并不是。任何一个类在外面不写的情况下,都会自动生成以下6个默认成员函数
1、初始化和清理:构造函数完成初始化工作,析构函数完成清理工作
2、拷贝复制:拷贝构造是使用同类对象初始化创建对象,赋值重载是把一个对象赋值给另外一个对象
3、取地址重载:主要是普通对象和const对象取地址

构造函数与析构函数

构造函数: 是一个特殊的成员函数,名字与类相同,创建类类型对象时由编译器自动调用,保证每一个数据成员都有一个合适的初始值,并且在对象的声明周期中只调用一次
需要注意构造函数的主要任务并不是开空间创造对象,而是初始化对象
特征:
1、函数名与类名相同
2、无返回值,也不写void
3、对象实例化时,编译器自动调用对应的构造函数
4、构造函数可以重载
5、如果类中没有显式定义构造函数,则c++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义后就不会再自动生成

构造函数的分类和调用:
分类
(按参数分类):无参构造(默认)和有参构造
(按类型分类):普通构造和拷贝构造
析构函数: 与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成。而是对象在销毁时自动调用析构函数,完成类的一些资源清理工作
特征:
1、析构函数名在类名前加~
2、无参数(不能发生重载),无返回值,也不写void
3、一个类有且只有一个析构函数,如果没有显式定义,系统会自动生成默认的析构函数
4、对象生命周期结束后,C++编译系统会自动调用析构函数
在这里插入图片描述
思路:
1、构造顺序:一定从整个程序的开始到结束:所以是 c a b d 注意此时static并不影响
2、由于程序结束时,是局部先结束,由于static修饰了d,此时可以把c和d的生命周期看做是一样的,所以是b先结束,其次是a(可以用栈来理解,先进后出,后进先出,局部变量是在栈里面)之后static(main函数结束生命周期结束)改变了作用域但是本质是还是局部的,所以结束d,最后就是结束c
因此顺序是B A D C

拷贝构造函数

只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
特征:
1、拷贝构造函数是构造函数的一个重载形式
2、拷贝构造函数的参数只有一个且必须使用引用传参,使用传值的话会引发无穷递归调用
3、若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝或者值拷贝

class Person
{
private:
    int _age;

public:
    Person()
    {
        cout << "Person的无参构造" << endl;
    }
    Person(int a)
    {
        _age = a;
        cout << "Person的有参构造" << endl;
    }
    
    // 拷贝构造函数
    Person(const Person& p) // 把p这个对象完完全全拷贝过来
    {
        // 将传入的人的属性全部拷贝到当前对象身上
        _age = p._age;
        cout << "Person的拷贝构造函数" << endl;
    }
    ~Person()
    {
        cout << "析构函数" << endl;
    }
};

int main()
{
    // 第一种调用方法:
    Person p1; // 调用默认构造的时候不要加()
    // Person p4(); // 编译器会认为它是函数声明
    Person p2(10); // 去调用有参构造
    Person p3(p2); // 调用拷贝构造函数


    // 第二种调用方法:
    Person p4;
    Person p5 = Person(10); // 有参构造 单独的Person(10)没有左边部分是匿名对象 因为没有名
    // 匿名对象特点: 用完后立马回收
    Person p6 = Person(p5); // 拷贝构造
    // c++默认Person(p3) == Person p3; 因此不要用拷贝构造函数来初始化匿名对象


    // 第三种调用方法:
    Person p7 = 10; // 相当于Person p7 == Person(10);
    Person p8 = p7; // 拷贝构造
    cout << "" << endl;
    return 0;
}

拷贝构造函数的使用时机:
1、使用一个已经创建完毕的对象来初始化一个对象
2、值传递的方式给函数传参
3、以值方式返回局部对象

在这里插入图片描述
此图return p1的时候,实际上是拷贝了一个新的对象返回给外面,因此两个地址不同

构造函数的调用规则:
1、如果用户定义了有参构造函数,则不在提供无参构造函数,但会提供默认拷贝构造函数
2、如果定义了拷贝构造函数,则不会在提供其他的构造函数

构造函数总结:
1、构造是默认成员函数,我们不写编译器会自动生成一份,我们写了的话编译器就不会生成,不写编译器就默认生成
2、构造函数对内置类型(int等)成员变量不做处理
3、对于自定义类型成员变量会调用它的默认构造函数
4、保证对象的初始化

析构函数总结:
1、完成对象资源的清理,如果类对象需要资源清理,才需要实现析构函数;如日期类没有资源需要清理,不用自己实现析构函数;如动态开辟的资源需要自己实现析构函数
2、我们不实现,编译器会生成默认的析构函数;实现了,编译器就不会生成了

拷贝构造总结:
1、使用同类型对象去初始化实例对象,参数必须是引用传参
2、设计问题:深浅拷贝
3、如果不实现,编译器会生成一份默认的拷贝构造函数,默认生成的拷贝构造会处理自定义类型和内置类型
4、内置类型完成按字节序的值拷贝–浅拷贝/值拷贝(相当于memcpy)
5、对于自定义类型的成员变量会去调用它的拷贝构造
6、日期类:不需要实现拷贝构造,默认生成的拷贝构造完成的值拷贝/浅拷贝就可以满足需求;栈类:需要自己实现,默认生成浅拷贝不能满足需求,需要自己实现深拷贝的拷贝构造(一个更改了会影响另外一个)

定义了拷贝构造一定要定义构造函数,不然会编译报错(因为定义了拷贝构造,系统就不会默认生成构造函数了)

运算符重载

运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通函数类似
函数名为:关键字operator后面接需要重载的运算符符号
函数原型为:返回值类型 operator操作符(参数)
注意:
1、不能通过连接其他符号去创建新的操作符,比如operator@
2、重载操作符必须有一个类类型参数
3、用于内置的运算符,其含义不可以进行改变,如内置的+不能改变它的含义
4、作为类成员的重载函数时,其形参看起来比操作数数目少一个成员函数的操作符有一个默认的形参this,限定为第一个形参
5、有五个运算符不能进行重载
::
sizeof
?:
.
.*

class Date
{
public:
    Date(int year = 0, int month = 1, int day = 1) // 构造函数
    {
        _year = year;
        _month = month;
        _day = day;
    }
    bool operator>(const Date &d) // 传引用,减少拷贝,const是为了防止他被修改
    {
        if (_year > d._year)
        {
            return true;
        }
        else if (_year == d._year && _month > d._month)
        {
            return true;
        }
        else if (_year == d._year && _month == d._month && _day > d._day)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1(2022, 3, 4);
    Date d2(2022, 3, 31);
    Date d3(2022, 4, 26);

    cout << (d1 > d2) << endl;
    // d1.operator>(d2)

    // 用一个已经存在的对象拷贝初始化一个马上创建实例化的对象:
    Date d4(d1);
    Date d5 = d1;
    

    return 0;
}

赋值运算符重载:
两个已经存在的对象之间进行赋值拷贝
注意:
1、参数类型
2、返回值
3、检测是否自己给自己赋值
4、返回*this
5、一个类如果没有显式定义赋值运算符重载,编译器也会默认生成一个
其中,编译器默认生成的赋值重载与拷贝构造做的事相似:
1、内置类型成员,会完成字节序值拷贝–浅拷贝
2、自动类型成员变量,会调用它的operator=

class Date
{
public:
    Date(int year = 0, 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; // d1 = d3之后要返回d1的值
    }
    bool operator>(const Date &d) // 传引用,减少拷贝,const是为了防止他被修改
    {
        if (_year > d._year)
        {
            return true;
        }
        else if (_year == d._year && _month > d._month)
        {
            return true;
        }
        else if (_year == d._year && _month == d._month && _day > d._day)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1(2022, 3, 4);
    Date d2(2022, 3, 31);
    Date d3(2022, 4, 26);

    cout << (d1 > d2) << endl;
    // d1.operator>(d2)

    // 用一个已经存在的对象拷贝初始化一个马上创建实例化的对象:
    Date d4(d1);
    Date d5 = d1;

    // 两个已经存在的对象是赋值拷贝:
    d2 = d1 = d3;

    return 0;
}
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}

int Date::GetMonthDay(int year, int month)
{
	// 判断该月多少天
	static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	int day = monthDayArray[month]; 

	if (month == 2 && ((year % 100 != 0) && (year % 4 == 0)) || (year % 400 == 0))
	{
		// 是闰年 2月29天
		day += 1;
	}

	return day;
}

void Date::Print()
{
	cout <<  _year << "-" << _month << "-" << _day << endl;
}

bool Date::operator>(const Date& d)
{
    if (_year > d._year)
    {
        return true;
    }
    else if (_year == d._year && _month > d._month)
    {
        return true;
    }
    else if (_year == d._year && _month == d._month && _day > d._day)
    {
        return true;
    }
    else
    {
        return false;
    }
}


// d1 += 100
Date& Date::operator+=(int day)
{
    _day += day;
    while (_day > GetMonthDay(_year, _month)) // 如果该月大于该月的有效天数,则要换算
    {
        _day -= GetMonthDay(_year, _month);
        ++_month;
        if (_month == 13)
        {
            _month = 1;
            ++_year;
        }
    }
    return *this; // 返回_day
}

// d1 + 100
Date Date::operator+(int day)
{
    Date ret(*this);
    ret += day;
    return ret; // 因为ret直接就销毁了 所以不传引用
}

// 前置++返回加了以后的值
Date& Date::operator++()
{
    *this += 1;
    return *this;
}

// 后置++返回加之前的值
Date Date::operator++(int)
{
    Date ret(*this);
    *this += 1;
    return ret;
}

日期类的实现:

// date.cpp文件
#define _CRT_SECURE_NO_WARNINGS 
#include "date.h"
Date::Date(int year, int month, int day)
{
    _year = year;
    _month = month;
    _day = day;
}

int Date::GetMonthDay(int year, int month)
{
    // 判断该月多少天
    static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    int day = monthDayArray[month];

    if (month == 2 && ((year % 100 != 0) && (year % 4 == 0)) || (year % 400 == 0))
    {
        // 是闰年 2月29天
        day += 1;
    }

    return day;
}

void Date::Print()
{
    cout << _year << "-" << _month << "-" << _day << endl;
}

bool Date::operator>(const Date& d)
{
    if (_year > d._year)
    {
        return true;
    }
    else if (_year == d._year && _month > d._month)
    {
        return true;
    }
    else if (_year == d._year && _month == d._month && _day > d._day)
    {
        return true;
    }
    else
    {
        return false;
    }
}


// d1 += 100
Date& Date::operator+=(int day)
{
    _day += day;
    while (_day > GetMonthDay(_year, _month)) // 如果该月大于该月的有效天数,则要换算
    {
        _day -= GetMonthDay(_year, _month);
        ++_month;
        if (_month == 13)
        {
            _month = 1;
            ++_year;
        }
    }
    return *this; // 返回_day
}

// d1 + 100
Date Date::operator+(int day)
{
    Date ret(*this);
    ret += day;
    return ret; // 因为ret直接就销毁了 所以不传引用
}

// 前置++返回加了以后的值
Date& Date::operator++()
{
    *this += 1;
    return *this;
}

// 后置++返回加之前的值
Date Date::operator++(int) 
{
    Date ret(*this);
    *this += 1;
    return ret;
}

// 这里实现==之后,后面的==可以复用这个函数
bool Date::operator==(const Date& d)
{
    return (_day == d._day && _month == d._month && _year == d._year);
}

bool Date::operator<(const Date& d)
{
    return !(*this >= d);
}
bool Date::operator>=(const Date& d)
{
    return *this > d || *this == d;
}
bool Date::operator<=(const Date& d)
{
    return !(*this > d);
}

bool Date::operator!=(const Date& d)
{
    return !(*this == d);
}
Date& Date::operator-=(int day)
{
    _day -= day;
    while (_day <= 0)
    {
        // 日期不合法,向上一位去借
        --_month;
        if (_month == 0) 
        {
            --_year;
            _month = 12;
        }
        _day += GetMonthDay(_year, _month);
    }
    return *this;
}
Date Date::operator-(int day)
{
    Date ret(*this);
    ret -= day;
    return ret;
}

Date& Date::operator--()
{
    *this -= 1;
    return *this;
}

Date Date::operator--(int)
{
    Date ret(*this);
    *this -= 1;
    return ret;
}

// 计算日期差有多少天
int Date::operator-(const Date& d)
{
    // 先假定左参数日期大于右参数对应的日期
    Date max = *this;
    Date min = d;
    int flag = 1;
    if (*this < d) // a - b (a<b)的情况,日期是小于0的
    {
        max = d;
        min = *this;
        flag = -1;
    }
    int count = 0;
    while (min != max) // 从小日期开始每次+1,如果等于大日期就是日期差
    {
        ++count;
        ++min;
    }
    return count * flag; // 因为小日期-大日期是负数
}

// 打印今天是周几
void Date::PrintWeekDay()
{
    const char* arr[] = { "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天" };
    int count = *this - Date(2020, 12, 7); // 2020年12月7日是周一
    cout << "今天是" << arr[count % 7] << endl;
}



// date.h
#pragma once
#include <iostream>

using namespace std;

class Date
{
public:
	Date(int year = 0, int month = 1, int day = 1);
	void Print();
	int GetMonthDay(int year, int month);
	bool operator>(const Date& d);
	bool operator<(const Date& d);
	bool operator>=(const Date& d);
	bool operator<=(const Date& d);
	bool operator==(const Date& d);
	bool operator!=(const Date& d);


	Date& operator+=(int day);

	// d1 + 100
	Date operator+(int day);

	Date& operator-=(int day);
	Date operator-(int day);
	int operator-(const Date& d);

	// ++d1;
	Date& operator++();

	// d1++; 后置为了跟前置++,进行区分
	// 增加一下参数占位,跟前置++,构成函数重载
	Date operator++(int);
	Date& operator--(); // 前置
	Date operator--(int); // 后置
	void PrintWeekDay();
private:
	int _year;
	int _month;
	int _day;
};

关于运算符重载的补充:

cout << d1; // 这样子写就是错误的,this指针默认是左操作数
//要实现下面这个函数需要这么写:
d1.operator<<(cout);
void Date::operator<<(ostream& out)
 // 这样写需要d1是左操作数,cout是右操作数
{

}

基于这种原因,要修改为全局,不是成员函数就没有this指针这些东西了:

// 问题:不能访问私有--解决方法:友元函数
class Date
{
	// 友元函数要放在类里面
	friend ostream& operator<<(ostream& out, const Date& d);
}
ostream& operator<<(ostream& out, const Date& d);//声明不放在类里面
ostream& operator<<(ostream& out, const Date& d)
{
    out << d._year << "/" << d._month << "/" << d._day << endl;
    return out;
}



const修饰的成员函数

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

	Date d1;
	d1.Print(); // 隐含了一个&d1 是 Date*

	const Date d2; // this指向的对象只读
	// d2.Print(); // 权限放大,不行
	// &d2 是 const Date*类型 是指向的对象不能修改

	/// 默认print函数里面this指针是Date* const this类型 是this本身不能修改
	// this 指向的对象可读可改

	// 解决办法:在类函数后面加const -- 前提不修改里面成员
	// void Date::Print() const; 声明与定义都要加

总结:
成员函数加const是好的,建议能加const的都加上,这样普通对象和const对象都可以调用了,但是如果要修改成员变量的成员函数是不能添加的,比如日期类中的+=, ++等实现

初始化列表

定义:开空间
声明:告诉名称/返回值/参数等

初始化列表:成员变量定义的地方,定义的时候就初始化了
相当于给成员变量找了一个依次定义处理的地方
int k = 1;
int x; x = 1;

初始化:以一个冒号开始,接着是以一个逗号分割的数据成员列表,每个成员变量后面跟一个放在括号里面的初始值或者表达式
[注意]
1、每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2、类中包含以下成员,必须放在初始化列表位置进行初始化
(a)引用成员变量
(b)const成员变量
(c)自定义类型成员(该类没有默认的构造函数)
3、尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表进行初始化
4、成员变量在类中的声明次序就是初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

class A
{
public:
	A (int a)
	{
		_a = a;
	}

private:
	int _a;
};

class Date
{
public:
	// 初始化列表:成员变量定义的地方
	// const、引用、没有默认构造函数的自定义类型成员变量必须在初始化列表进行初始化
	// 因为他们必须在定义的时候初始化
	// 对于其他类型成员变量如int _year在哪里初始化都可以
	Date(int year, int month, int day, int i)
		: _N(10)
		, _ref(i)
		, _aa(-1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	// 声明
	int _year;
	int _month;
	int _day;
	const int _N;
	int& _ref; // 引用
	A _aa;
};

int main()
{
	int i = 0;
	Date d1(2022, 1, 19, i);
	return 0;
}

例题:

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main() 
{
	A aa(1);
	aa.Print();
}

以上程序输出什么? 答案:1 随机值
因为_a2先声明,所以先执行_a2(_a1),但是此时a1是随机值,所以_a2也是随机值,之后才声明的_a1,在执行_a1(a)因此值是1

explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换作用
使用explicit修饰构造函数,将会禁止单参构造函数的隐式转换
explicit只能在声明中出现,不能写在定义中

class Date
{
public:
	Date(int year)
		:_year(year)
	{

	}
	

private:
	int _year;
};

int main()
{
	Date d1(2022);
	Date d2 = 2022; // 隐式类型转换 整型转化为日期类()
	// 用2022构造一个临时对象Date(2022)然后再用这个对象构造d2
	// 先构造一个临时变量,然后临时变量在构造d2 -- 2次拷贝构造(编译器把它优化为1次构造)

	// 隐式类型转换 - 相近类型 -意义相近的类型
	double d = 1.1;
	int i = d;
	const int& i = d;

	// 强制类型转换 - 无关类型
	int* p = &i;
	int j = (int)p;

	return 0;
}

如果不想让以上隐式类型转换发生 就加explicit关键字

class Date
{
public:
	explicit Date(int year)
		:_year(year)
	{

	}
	

private:
	int _year;
};

int main()
{
	Date d1(2022);
	Date d2 = 2022; // 隐式类型转换 整型转化为日期类()
	// 用2022构造一个临时对象Date(2022)然后再用这个对象构造d2
	// 但是C++编译器在这个连续的过程中,多个构造会被优化,合二为一
	// 所以这里被优化后就直接是一个构造(单参数的构造函数)

	// 隐式类型转换 - 相近类型 -意义相近的类型
	double d = 1.1;
	int i = d;
	const int& i = d;

	// 强制类型转换 - 无关类型
	int* p = &i;
	int j = (int)p;

	return 0;
}

static成员

声明为static的类成员称为类的静态成员, 用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数称为静态成员函数。静态成员变量一定要在类外进行初始化
特性:
1、静态成员为所有类对象所共享,不属于某个具体的实例
2、静态成员变量必须在类外定义,定义时不添加static关键字
3、类静态成员即可用类名::静态成员或者对象.静态成员来访问
4、静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5、静态成员和类的普通成员一样,也有public,protected,private三种访问级别,也具有返回值

// 调一次构造函数就++一次,计算总共几次,可以用static
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		++_sCount;
	}

private:
	int _a;
	// s表示static

public:
	static int _sCount; // 静态成员变量属于整个类,所有对象
	// 生命周期在整个程序运行期间

};
int A::_sCount = 0; // 定义初始化
int main()
{
	A a1;
	A a2 = 1;
	// f(a1);
	cout << a1._sCount;
	return 0;
}
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		++_sCount;
	}
	int GetCount()
	{
		return _sCount;
	}
	

private:
	int _a;
	// s表示static

public:
	static int _sCount; // 静态成员变量属于整个类,所有对象
	// 生命周期在整个程序运行期间

};
int A::_sCount = 0; // 定义初始化
int main()
{
	A a1;
	A a2 = 1;
	// f(a1);

	// 类外面访问的方式(共有)
	cout << A::_sCount << endl;
	// 它属于所有对象
	cout << a1._sCount << endl;
	cout << a2._sCount << endl;

	// 私有--只能提供一个共有的成员函数才能访问
	cout << a1.GetCount() << endl;
	cout << a2.GetCount() << endl;

	// 不用对象就可以访问到的方式:静态成员函数
	// 静态成员函数的特点:
	// 没有this指针,只能访问静态成员变量和成员函数


	return 0;
}
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		++_sCount;
	}
	/*int GetCount()
	{
		return _sCount;
	}*/
	static int GetCount()
	{
		// 不能访问_a,因为没有this指针
		return _sCount;
	}

private:
	int _a;
	// s表示static

public:
	static int _sCount; // 静态成员变量属于整个类,所有对象
	// 生命周期在整个程序运行期间

};
int A::_sCount = 0; // 定义初始化

例题:求1+2+…+n

C++11成员初始化的新玩法

class B
{
public:
	B(int b = 0)
		:_b(b)
	{

	}

private:
	int _b;
};
class A
{
public:
	A(int a1)
	{
		_a1 = a1;
	}

private:
	// 声明:
	int _a1 = 0; // 这里不是初始化-是缺省值
	// 如果在初始化列表阶段中,没有对成员变量初始化,就会使用缺省值
	B _bb1 = 10;
	B _bb2 = B(20);
};
int main()
{
	A aa(1);

	return 0;
}

友元

友元分为:友元函数,友元类
友元函数
友元提供了一种突破封装的方式,有时提供了便利,但是友元会增加耦合度、破坏了封装,所以友元不宜多用(能不用就不用)
说明:
1、友元函数可以访问类的私有和保护成员,但不是类的成员函数(就是全局函数)
2、友元函数不能用const修饰–const修饰非静态的成员函数,修饰this指向的对象,友元只是一种声明
3、友元函数可以在类定义的任何地方声明,不受类访问限定符的限制
4、一个函数可以是多个类的友元函数
5、友元函数的调用与普通函数的调用和原理相同

// 问题:不能访问私有--解决方法:友元函数
// 一个全局函数想要访问私有或者保护
ostream& operator<<(ostream& out, const Date& d)
{
    out << d._year << "/" << d._month << "/" << d._day << endl;
    return out;
}

想让这个函数访问Date,定义friend


class Date
{
	// 友元函数要放在类里面
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 0, int month = 1, int day = 1);
	void Print();

	//void operator<<(ostream& out);
	// 友元函数--可以用私有和保护的成员
private:
	int _year;
	int _month;
	int _day;
};
// 放全局
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

友元类
一个类想要访问另外一个类的私有或者保护

class Time
{
	// 表明日期类是Time类的朋友
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{

	}

private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量-使用友元类
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}

private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

注意:
A是B的友元,B是C的友元,但是A与C不是友元,他们之间没有关系

内部类

如果一个类定义在另外一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员,但是外部类不是内部类的友元
特性:
1、内部类可以定义在外部类的public、protected、private都可以
2、注意内部类可以直接访问外部类中的static、枚举成员、不需要外部类的对象/类名。
3、sizeof(外部类) = (外部类的大小),和内部类没有任何的关系,因为内部类相当于是定义在全局的。其中static也不占大小,static的变量在静态区

class A
{
private:
	static int k;
	int h;
public:
	// 内部类
	// 1、内部类B和在全局定义是基本一样的,只是他的受外部类A类域限制,定义在A的类域中
	// 2、内部类B天生就是外部类A的友元,也就是B中可以访问A的私有,A不能访问B的私有
	class B
	{
		// friend class A;
	public:
		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}

	private:
		int _b;
	};
	
	void f(B bb)
	{
		// A不是B的友元,不能访问B
		bb._b;
	}
};

int A::k = 1;

int main()
{
	cout << sizeof(A) << endl;
	A aa;
	A::B bb;

	return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值