- 代码将会放到: https://gitee.com/liu-hongtao-1/c–c–review.git ,欢迎查看;
- 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识;
- 文章多为学习笔记,以综述学习的重点为主,可能有一些细节没有提及或把握不到位,感谢理解;
class className {
// 类体:由成员函数和成员变量组成
};//注意分号
声明和定义都在类体中,如果直接在类体定义中,编译器会将类内函数作为内联函数。因此短小函数,适合作为内联函数,适合在类内定义
struct Student{
void Set(const char* name, const char* gender, int age){
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Print(){
cout << _name << " " << _gender << " " << _age << endl;
}
char _name[20];
char _gender[3];
int _age;
};
声明和定义分离:声明放在头文件中,定义放在源文件中
//.h
#include
using namespace std;
struct Student{
void Set(const char* name, const char* gender, int age);
void Print();
char _name[20];
char _gender[3];
int _age;
};
注意加上Student::
,表示为Student类中的成员函数
//.cpp
#include"test.h"
void Student::Set(const char* name, const char* gender, int age){
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void Student::Print(){
cout << _name << " " << _gender << " " << _age << endl;
}
包括三种:public(公有)、protected(保护)、private(私有)
说明:
struct
和class
的区别:
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 ::
作用域解析符指明成员属于哪个类域。
声明:类的声明只是限定了类的成员变量和成员函数,相当于一个模型,并没有分配实际内存空间来存储
实例化:用类创建对象的过程,实例化出的对象是需要占用内存空间的
存储分布:成员变量存储在对象中,而成员函数不是,放在了代码段中。因为每个对象的成员变量是不一样的,需要独立存储,而成员函数是公共的,如果放在对象中就会浪费内存。
注意:如果实例化的类仅有成员函数或是一个空类,实例化大小就是1B,用于占位,不存储有效数据,标识对象被实例化定义出来了。
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
C++是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态。C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,对于外部用户内部细节是透明的。
作用:C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
显示使用
注意
this指针的类型:类类型* const 或者 类类型* (这里没太弄清楚,应该是和成员函数的类型相关)
只能在“成员函数”的内部使用
存在于this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参,所以对象中不存储this指针。一般情况存在于栈中,而对于vs编译器通过ecx
寄存器自动传递,不需要用户传递。
编译器工作:增加一个隐藏的指针参数、用this指针访问成员变量
void Init(int year, int month, int day){
_year = year;
_month = month;
_day = day;
}
void Init(int year, int month, int day)const {
_year = year;
_month = month;
_day = day;
}
void Init(Date* this,int year, int month, int day){
this->_year = year;
this->_month = month;
this->_day = day;
}
void Init(Date* const this,int year, int month, int day){
this->_year = year;
this->_month = month;
this->_day = day;
}
空指针问题:Date* ptr = nullptr;
,若将类的指针变量赋予一个空指针,可能会出现程序崩溃的问题。问题的发生在于编译器是否对于空指针有必要进行解引用。
从以下代码进行分析,对于ptr->func();
与(*ptr).func();
两句代码,是直接访问进程地址空间中代码段的数据,因此不需要在栈中进行解引用,故而可以正常运行;对于ptr->Init(2022, 2, 2);
,需要访问类的成员函数,需要对nullptr
进行解引用,而无法对空指针进行解引用,因此程序会崩溃。
class Date{
public:
// 定义
void Init(int year, int month, int day){
cout << this << endl;
this->_year = year;
this->_month = month;
this->_day = day;
}
void func(){
cout << this << endl;
cout << "func()" << endl;
}
//private:
int _year; // 声明
int _month;
int _day;
};
int main(){
Date d;
d.Init(2022, 2, 2);
Date* ptr = nullptr;
//ptr->Init(2022, 2, 2); // 运行崩溃
ptr->func(); // 正常运行,
(*ptr).func(); // 正常运行,
return 0;
}
可以从汇编的角度去理解,更为清晰。对应操作为,传递一个this指针,然后对于call的调用,是直接访问Date的函数func,而不需要进行解引用。
ptr->func(); // 正常运行,
00ED585A 8B 4D E0 mov ecx,dword ptr [ptr]
00ED585D E8 E5 BB FF FF call Date::func (0ED1447h)
(*ptr).func(); // 正常运行,
00ED5862 8B 4D E0 mov ecx,dword ptr [ptr]
00ED5865 E8 DD BB FF FF call Date::func (0ED1447h)
六个默认成员函数
定义:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
使用:
class Date{
public:
// 1.无参构造函数
Date(){}
// 2.带参构造函数
Date(int year, int month, int day){
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
默认构造:
编译器会自动生成一个默认构造函数,但当实现任一个构造函数后,编译器将不会自动生成一个默认构造函数
默认生成构造函数,内置类型成员不会被处理,自定义类型成员会调用相应的构造函数
如此规则是存在缺陷的,因此在C++11中,可以在声明位置给予缺省值
class Date{
public:
Date(){}
private:
int _year = 2023;
int _month = 2;
int _day = 1;
};
注意:
函数名与类名相同
没有返回值
在对象实例化时,编译器自动调用构造函数
构造函数可以重载,可以有多个构造函数。
一般条件下可以提供缺省参数,调用时要注意构造函数调用不明确的问题。
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。(默认构造函数:不传参就可以调用的函数,建议每个类都提供一个)
class Date{
public:
Date(int year = 2023, int month = 2, int day = 1){
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
定义:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。 析构函数名是在类名前加上字符 ~
。
使用:
class Date{
public:
Date(){
cout << "Date()" << endl;
}
~Date() {
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
默认析构函数:
注意:
定义:只有单个形参,该形参是对本类类型对象的引用(常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
原因:cpp中内置类型编译器会直接拷贝,而自定义类型需要调用拷贝构造。自定义类型无法承担直接拷贝的责任,比如对于一块开辟了的堆区空间,如果拷贝后,两个实例退出作用域,会析构两次,从而发生错误。
使用:如果自己实现了析构函数释放空间(或者说是实现了资源管理),就需要实现拷贝构造
Date(const Date& date) {
_year = date._year;
_month = date._month;
_day = date._day;;
}
Date d2(d1);
Date d3 = d1;
默认生成的拷贝构造:默认拷贝无论对于内置类型是浅拷贝,自定义类型会调用它的拷贝函数,而有时这种拷贝方式可能会造成一些错误,需要重新定义拷贝函数。
注意:
无穷递归问题:拷贝构造函数不可以是传值传参
Date(Date date) {
_year = date._year;
_month = date._month;
_day = date._day;;
}
原因:Date(Date date)
传值传参需要拷贝出一个临时变量,而cpp自定义类型需要调用拷贝构造,从而会调用Date(Date date)
,此时成了一个无穷递归的问题。因此必须要引用传参来打破这个无穷递归问题。
拷贝方向:最好加上const
,这样就不会出现反方向拷贝了
区分Date(const Date& date)
与Date(const Date* date)
:前者为一个拷贝构造函数,而后者为一个构造函数
作用:增强程序的可读性
使用:
返回值类型 operator操作符(参数列表){
//……
}
注意:
运算符重载和函数重载:
函数重载:支持函数名相同,参数不同的函数可以同时使用
运算符重载:自定义类型的对象可以使用运算符
若果是两个操作数,则参数列表:(左操作数, 右操作数)
不能通过连接其他符号来创建新的操作符
重载操作符必须有一个类类型或者枚举类型的操作数
用于内置类型的操作符,其含义不能改变
作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的,操作符有一个默认的形参this,限定为第一个形参
.*
、::
、sizeof
、?:
、.
以上5个运算符不能重载
后置++
与前置++
的区别在于,后置需要传递一个int
参数使二者区分
classType& operator=(const classType& className){
if(this == &className){
return *this;
}
//……
return *this;
}
注意:
this
指针,另外一个参数最好使用引用,从而减少拷贝带来的开销*this
classType className1 = className2
为拷贝构造,赋值重载是针对两个已经实例化的对象,拷贝构造是初始化实例的过程。默认赋值函数:完成对象按字节序的值拷贝
以Date类示例来编写,实际上没有太多的价值
Date* operator&(){
return this ;
}
以Date类示例来编写,实际上没有太多的价值
const Date* operator&()const{
return this ;
}
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。在内部不改变成员变量的成员函数时,最好加上const
。
void Print() const{
cout << "Print()const" << endl;
}
//编译器处理后
void Print(const Date* this) {
cout << "Print()const" << endl;
}
实例:如果调用d2.Print()
传递的this
指针不是const
,将会导致权限扩大的问题,编译器将会报错,故需要在Print()
设置为const
成员函数
class Date{
public:
Date(int year, int month, int day){
_year = year;
_month = month;
_day = day;
}
void Print(){
cout << "Print()" << endl;
}
void Print() const{
cout << "Print() const" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main(){
Date d1(2023, 1, 1);
d1.Print();//Print()
const Date d2(2023, 1, 1);
d2.Print();//Print() const
}
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值
Date::Date(int year, int month, int day) {
if ((month <= 0 || month > 12) || (day < 0 || day > GetMonthDay(year, month))) {
cout << "日期非法" << endl;
}
_year = year;
_month = month;
_day = day;
}
注:
作用:由于const
或者引用必须要在定义出初始化需要给每一个成员变量找一个定义变量的位置。同时调用构造函数时对自定义类型只会调用其默认构造函数。
使用:在构造函数中,冒号开始,逗号分割
class Date{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意:
const
、引用、缺少默认构造函数的对象作用:对于单个参数的构造函数,还具有类型转换的作用,explicit修饰构造函数,将会禁止单参构造函数的隐式转换。
实例:
class Date {
public:
//Date(int year)
// : _year(year)
//{}
explicit Date(int year)
: _year(year)
{}
private:
int _year;
};
补充:多参数的c++构造函数的隐式类型转换
class Date {
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1 = { 1 , 1, 1 };
d1.Print();
}
作用:声明为static的类成员称为类的静态成员:
使用:
静态的成员变量一定要在类内声明,类外进行初始化
class A {
private:
static int count; // 声明
};
int A::count = 0; // 定义初始化
静态成员变量访问
类静态成员,可用类名::静态成员
或者对象.静态成员
来访问,但是静态成员和类的普通成员一样,也有public
、protected
、private
访问级别。
类内函数
class A {
private:
static int count;
public:
int GetCount() {
return count;
}
};
静态成员函数
静态成员函数:没有传递this指针,无需对对象进行解引用,可以不初始化实例进行调用。同时,静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
class A {
private:
static int count;
public:
static int GetCount() {
return count;
}
};
int A::count = 0;
int main() {
A a1;
cout << a1.GetCount() << endl;
cout << A::GetCount() << endl;
A *a2 = nullptr;
cout << a2->GetCount() << endl;
}
注:
作用:提供了一种突破封装的方式,有时提供了便利
分类:友元函数和友元
注意:友元会增加耦合度,破坏了封装,所以友元不宜多用
作用:友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend
关键字。
注:
const
修饰的是*this
,只有非静态的成员函数才能用const
实例:
class Date {
public:
//友元
friend ostream& operator<<(ostream& out, const Date& date);
friend istream& operator>>(istream& in, Date& date);
//.....
private:
int _year;
int _month;
int _day;
};
inline ostream& operator<<(ostream& out, const Date& date) {
out << date._year << "/" << date._month << "/" << date._day;
return out;
}
inline istream& operator>>(istream& in, Date& date) {
in >> date._year >> date._month >> date._day;
return in;
}
作用:友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
注意:
实例:
class Time{
//友元类
friend class Date;
public:
Time(int hour, int minute, int second)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date{
public:
Date(int year = 2024, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTime(int hour, int minute, int second){
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
定义:如果一个类定义在另一个类的内部,这个内部的类就叫做内部类。
特点:
sizeof(外部类)
,和内部类没有任何关系。实例:
class Time{
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
// 内部类
class Date {
public:
Date(int year = 2024, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void Print(const Time& t) {
cout << _year << "/" << _month << "/" << _day << " " << t._hour <<
":" << t._minute << ":" << t._second << " id:" << _creator << endl;
}
private:
int _year;
int _month;
int _day;
};
private:
int _hour;
int _minute;
int _second;
static string _creator;
};
string Time::_creator = "Jerry";
int main() {
Time::Date d1;
d1.Print(Time());
}
作用:有时需要对类内函数进行调用时,如果重新定义初始化会显得麻烦,因此引用了匿名对象,匿名对象生命周期只在代码的一行,较为方便。
private:
int _a;
public:
A(int a)
:_a(a)
{}
};
class Solution {
public:
int my_Solution() {
cout << "my_Solution" << endl;
return 1;
}
~Solution(){
cout << "~Solution()" << endl;
}
};
// 使用实例
A f(){
int ret = Solution().my_Solution();
return A(ret);
}
int main(){
Solution s;
// 匿名对象
Solution();
cout << s.my_Solution() << endl;
// 使用实例
cout << Solution().my_Solution() << endl;
f();
}
总结:
//Date.ch
#pragma once
#include
#include
using namespace std;
class Date {
public:
//友元
friend ostream& operator<<(ostream& out, const Date& date);
friend istream& operator>>(istream& in, Date& date);
int GetMonthDay(int year, int month);
//知识点:
//1.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
//2.一般条件下可以提供缺省参数,调用时要注意构造函数调用不明确的问题
//3.缺省参数不能在函数声明和定义中同时出现。若声明和定义进行分离,需要使用缺省参数,必须在声明中给出。
Date(int year = 2024, int month = 1, int day = 14);
// 析构函数
~Date();
// 拷贝构造
//易错点:拷贝构造函数不可以是传值传参;最好加上`const`,这样就不会出现反方向拷贝了
Date(const Date& date);
void Print();
//运算符重载
bool operator==(const Date& date);
bool operator!=(const Date& date);
bool operator>(const Date& date);
bool operator<(const Date& date);
bool operator>=(const Date& date);
bool operator<=(const Date& date);
Date& operator+=(const int day);
Date operator+(const int day);
Date& operator-=(const int day);
//知识点:函数重载
Date operator-(int day);
int operator-(const Date& date);
//自增 自减
Date& operator++();
// 后置,需要设置一个整型参数
Date operator++(int);
Date& operator--();
// 后置,需要设置一个整型参数
Date operator--(int);
private:
int _year;
int _month;
int _day;
};
流插入
//ostream& operator<<(ostream& out, const Date& date);
流提取
//istream& operator>>(istream& in, Date& date);
inline ostream& operator<<(ostream& out, const Date& date) {
out << date._year << "/" << date._month << "/" << date._day;
return out;
}
inline istream& operator>>(istream& in, Date& date) {
in >> date._year >> date._month >> date._day;
return in;
}
//Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
int Date::GetMonthDay(int year, int month){
assert(!(month <= 0 || month > 12));
int monthArray[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
monthArray[2] = 29;
}
return monthArray[month];
}
Date::Date(int year, int month, int day) {
if ((month <= 0 || month > 12) || (day < 0 || day > GetMonthDay(year, month))) {
cout << "日期非法" << endl;
}
_year = year;
_month = month;
_day = day;
//cout << "Date(int year, int month, int day)" << endl;
}
Date::~Date() {
//cout << "~Date()" << endl;
}
Date::Date(const Date& date) {
_year = date._year;
_month = date._month;
_day = date._day;
//cout << "Date(const Date& date) " << endl;
}
void Date::Print() {
cout << _year << "/" << _month << "/" << _day << endl;
}
bool Date::operator==(const Date& date) {
if (_year == date._year && _month == date._month && _day == date._day) {
return true;
}
else return false;
}
bool Date::operator!=(const Date& date) {
return !(*this == date);
}
bool Date::operator<(const Date& date) {
if (_year < date._year) {
return 1;
}
else if (_year == date._year && _month < date._month) {
return 1;
}
else if (_year == date._year && _month == date._month && _day < date._day) {
return 1;
}
return 0;
}
bool Date::operator<=(const Date& date) {
return (*this < date) || (*this == date);
}
bool Date::operator>(const Date& date) {
return !(*this <= date);
}
bool Date::operator>=(const Date& date) {
return (*this < date);
}
Date& Date::operator+=(const int day) {
if (day < 0) {
*this -= (-day);
return *this;
}
else {
_day += day;
while (_day > GetMonthDay(_year, _month)) {
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13) {
_year++;
_month = 1;
}
}
return *this;
}
}
// 函数复用
Date Date::operator+(const int day) {
Date temp = *this;
temp += day;
return temp;
}
Date& Date::operator-=(const int day) {
if (day < 0) {
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0) {
//借上个月 需要对月先处理
_month--;
if (_month == 0) {
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator-(int day) {
Date temp = *this;
temp -= day;
return temp;
}
int Date::operator-(const Date& date) {
Date max = *this;
Date min = date;
int flag = 1;
if (*this < date) {
max = date;
min = *this;
flag = -1;
}
int gap = 0;
while (min != max) {
++min;
++gap;
}
return gap * flag;
}
Date& Date::operator++() {
*this += 1;
return *this;
}
Date Date::operator++(int) {
Date temp = *this;
*this += 1;
return temp;
}
Date& Date::operator--() {
*this -= 1;
return *this;
}
// 后置,需要设置一个整型参数
Date Date::operator--(int) {
Date temp = *this;
*this -= 1;
return temp;
}