从类和对象开始学好C++

it2026-08-24  2

#

学习C++,首先要了解的便是它和C语言的区别所在,很多的同学可能和我一样,学校是先开设了C语言,之后才开设的C++课程,因此学习C++之前,我们要明白:

**1.C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制)。

2.C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。 所以C与C++的最大区别在于它们的用于解决问题的思想方法不一样。之所以说C++比C更先进,是因为“ 设计这个概念已经被融入到C++之中 ”。**

类与对象(上)

01 .类的引入及定义

两种定义类 1.struct 类名 { } 2. class 类名 {},这里我们重点介绍的是后面一种。

class Student { public: //公有的成员在类外可见 //成员函数 void display() {} void setId(int id) { _id = id; } void setNumber(int number) { _number = number; } private: //私有的成员在类外不可见 //成员变量, 属性 int _number; int _id; protected: // 保护的成员在类外不可见 char name[10]; char gender[10]; char major[10]; };

那么问题来了struct和class的区别是什么呢? 答: C++需要兼容C语言,所以C++中的struct可以当成结构体去进行使用,也可以用来定义类;它和class定义类是一样得,区别在于struct的成员默认访问方式是public,class的成员默认访问方式是private。

02 .类的访问限定符及封装

上面我们所看到的public和protected,private都是类的访问限定符对于访问限定符的说明:

public修饰的成员在类外可以直接被访问 protected和private所修饰的成员在类外不能直接被访问(protected和private是类似的) 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止 class的默认访问权限为private,struct的默认访问权限为public 封装:用类将成员和函数将合在一起,使得对象更加的完善,通过访问权限选择性的将接口提供给外部用户来进行使用,实质上是一种管理。

03 .类的作用域和类的实例化

如果在类体外定义成员的话,需要使用::作用域解析符指明成员属于那个类域

class B { private: int _a; void fun() { cout << "class B fun()" << endl; } void fun2(); }; void B::fun2() {//类域之中的fun2的定义 cout << "B::fun2()" << endl; }

类的实例化:

1.创建一个类类型变量的过程称之为类的实例化 2.相当于类只是一张设计图,通过类创建出的变量才是将其变换成一个真 正的实物 3.实例化前的类不占据空间 **

04 .类对象模型

**

类的大小也遵循内存对齐的规则:并且在类中嵌套类的时候,如果此类没有创建变量,不计算其内存大小

class G { char _c; //1 double _d; // 16 int _a; // 20 char _c2; //21 //24 //嵌套类本身遵循内存对齐的原则,计算大小: H: 24 class H { double _d; //8 char _c; //9 int _a; //16 char _c1; //17 //24 }; H _h;//未创建变量时,不计算内存大小 };

05 .this指针

1.this指针的用处: 一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。 this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。例如,调用date.SetMonth(9) <===> SetMonth(&date, 9),this帮助完成了这一转换 . 在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无需通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看成this的隐式使用。 this的目的总是指向这个对象,所以this是一个常量指针,我们不允许改变this中保存的地址。

如果你觉得不好理解,那么关于this指针有一个很经典的回答: 当你进入一个房子后, 你可以看见桌子、

椅子、地板等, 但是房子你是看不到全貌了。 对于一个类的实例来说, 你可以看到它的成员函数、成员变量, 但是实例本身呢? this是一个指针,它时时刻刻指向你这个实例本身。

类与对象(中)

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

如果一个类中什么成员都没有,简称为空类。空类中并不是什么都没有,任何一个类在外面不写的情况下,都会自动生成下面的6个默认成员函数。

0.1 构造函数

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名为构造,但其主要任务并不是开空间创建对象,而是初始化对象

函数名与类名相同,无返回值,对象实例化时编译器会自动调用所对应的构造函数,构造函数可以重载 如果类中存在自定义成员,则构造函数会自动调用自定义成员的默认构造完成初始化,如果自定义成员没有默认构造则会产生编译错误 默认构造只能够存在一个,在声明一个函数的时候,是不会调用无参构造的

编译器默认生成的构造函数 Date() { } 显示定义的无参构造 Date() { _year = y; _month = m; _day = d; } 全缺省的构造函数 Date(int y = 2020, int m = 5, int d = 20) { _year = y; _month = m; _day = d; } 重载构造函数 Date(float f) { }

explicit关键字 构造函数对于单个参数的构造函数还具有类型转换的作用(调用构造创建一个匿名对象,通过匿名对象来给所需要创建的对象进行赋值或拷贝构造),而为了避免这种隐式转换,可以使用explicit关键字来修饰构造函数,将会禁止单参构造函数的隐式转换。

**构造函数初始化列表

```cpp 在这里插class Time { public: Time(int a = 1) :_a(a) { cout << "Time(int)" << endl; } private://这里是成员变量声明的地方,而引用和const类型变量,这两者定义时必须初始化 int _a; }; 入代码片

1.类中必须放在初始化列表中的有引用成员变量,const成员变量,自定义类型成员(没有默认构造函数),其他成员可以不进行显示初始化 2.每个成员变量在初始化列表中只能够出现一次,因为初始化列表是对象的成员变量定义的地方 3.成员变量在初始化列表中初始化的顺序,必须和声明顺序一致,与其在初始化列表中的顺序无关(最好保持初始化列表和声明顺序一致)

0.2析构函数

因为类的一些资源并不在类中,因此在对象生命周期结束的时候需要对资源进行清理和释放(时清理资源不是销毁对象),则会自动调用析构函数,完成资源清理的工作。

析构函数名 在类名前加上取反符号 ~,没有参数也没有返回值 一个类有且只有一个析构函数,若未进行显式定义,系统会自动生成默认的析构函数 在对象生命周期结束时,编译系统会自动调用析构函数 如果没有资源需要清理,可以不用显式写析构函数,直接使用编译器默认生成的析构函数即可

在这里插入代码片class A { public: ~A() { cout << "~A()" << endl; } int _a; };

0.3 拷贝构造函数

拷贝构造是构造函数的一个重载形式,它是用一个已经存在的对象去创建一个新的对象,创建的新对象和当前所存在的对象内容完全相同;

class Date { public: //构造函数 Date(int y = 1, int m = 1, int d = 1) { _year = y; _month = m; _day = d; } //拷贝构造函数 Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; void Test() { Date d; Date d2(2020, 4, 1); Date& rd = d; Date copy1(d); Date copy2(Date(2020, 5, 20));//优化,直接调用构造函数创建copy2 //不优化:调用构造创建匿名对象,+ 拷贝构造 }

若没有显式定义拷贝构造函数的话,系统会默认生成拷贝构造函数,但智慧按照内存存储的字节序完成浅拷贝,拷贝对象模型中的内容,不会拷贝资源,因此如果需要拷贝资源一定要显式定义拷贝构造函数。

拷贝构造函数的参数只有一个(一般用const修饰),必须使用引用传参,如果使用传值方式的话会引发无穷次的递归调用。

0.4运算符重载

运算符重载是具有特殊函数名的函数,也具备其返回值的类型,函数名,参数列表,其返回值类型和参数列表与普通的函数类似,其作用旨在增强代码的可读性,定义和使用与普通函数一致。

class Date { public: bool IsEqual(const Date& d) { return _year == d._year && _month == d._month && _day == d._day; } bool operator==(const Date& d) {//底层接口 bool operator==(Date* const this, const Date& d) return _year == d._year && _month == d._month && _day == d._day; } private: int _year; int _month; int _day; }; //输出运算符重载函数 ostream& operator<<(ostream& _cout, const Date& date) { _cout << date._year << " " << date._month << " " << date._day << endl; return _cout; }

重载操作符必须有一个类类型或枚举类型的操作数,不能通过连接其他符号来创建新的操作符 作为类成员的重载函数时,成员函数的第一个操作符默认为形参this . ,::,sizeof,?:, .以上5个运算符不能重载,笔试选择题中较为热门*

运算符的重载

class Date { public: Date(int y = 1, int m = 1, int d = 1) { _year = y; _month = m; _day = d; } Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; cout << "Date(const Date& d)" << endl; } //优化 ,避免自己给自己赋值 Date& operator=(const Date& d2) { //判断是否给自己赋值 if (this != &d2) { _year = d2._year; _month = d2._month; _day = d2._day; } cout << "operator=(const Date& d2)" << endl; //返回当前调用此函数的对象本身 return *this; } //private: int _year; int _month; int _day; }; void Test() { Date d(2020, 5, 22); Date d2(2019, 1, 1); //如果对象都存在,调用赋值运算符重载函数,如果左边对象不存在,则调用拷贝构造 d2 = d; d2.operator==(d);// 同上等价 d2 = d2; Date d3(2018, 10, 1); //连续赋值:从右向左赋值 d = d2 = decltype; d.operator=(d2.operator=(d3));//同上面等价 Date d4 = d3;//因为d4不存在,则调用拷贝构造,用d3创建d4对象 }

1.赋值运算符重载函数d=d2,修改已经存在的对象内容,不是去创建新的对象 2.如果当前类中有资源存在,必须显式定义赋值运算符重载函数完成深拷贝,否则会采用编译器默认生成的字节拷贝,只能够浅拷贝 3.如所需赋值的对象存在,则直接调用赋值运算符重载函数,如所需赋值对象不存在,则直接调用拷贝构造

0.5 const成员函数

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

const函数和非const函数

void printD() // 等价于 printD(Date* const this) { cout << _year << " " << _month << " " << _day << endl; //可以修改内容 this->_year = 100; //可以调用const成员函数 fun(); } void printD() const //等价于 printD(const Date* const this) { cout << _year << " " << _month << " " << _day << endl; //不能修改内容 this->_year = 100;这是错误的 //不能调用非const成员函数,读写的权限不能被放大 fun() //不能进行自加操作 ++*this; } void fun()const { }

0.6取地址

```cpp class Date { public : Date* operator&() { return this ; } const Date* operator&()const { return this ; } private : int _year ; // 年 int _month ; // 月 int _day ; // 日 }; 这两个运算符一般不需要重载使用编译器默认生成的即可,如果想要让别人获取到指定的内容的话,才需要进行重载。 # 02.六大成员函数总结对比 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201022194558182.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0ODc3MTM1,size_16,color_FFFFFF,t_70#pic_center) ## 类与对象(下) ## 01.静态成员 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201022194724808.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0ODc3MTM1,size_16,color_FFFFFF,t_70#pic_center) ```cpp //int cnt = 0; //定义全局变量的话,安全性较低,容易篡改 class Date { public: Date(int year = 2020, int month = 12, int day = 20) :_year(year) , _month(month) , _day(day) { ++cnt; cout << "Date(int ,int ,int)" << endl; } Date(const Date& d) :_year(d._year) , _month(d._month) , _day(d._day) { ++cnt; cout << "Date(const Date&)" << endl; } //静态成员函数:函数内部没有this指针 static int getCount() { } void Display() { cout << _year << "-" << _month << "-" << _day << endl; getCount(); cout << cnt << endl; } private: int _year = 1; int _month = 1; int _day = 1; public: static int cnt; }; //静态成员必须在类外初始化 int Date::cnt = 0; Date fun(Date d) //拷贝构造 { cout << &d.cnt << endl; return d; } void Test() { Date d;//构造 Date d2 = fun(d);//拷贝函数 fun:进行优化,只有两次拷贝构造,传参创建d2 //静态成员变量/静态成员函数访问方式: // 1. 对象访问 cout << d.getCount() << endl; cout << d2.getCount() << endl; cout << &d.cnt << endl; cout << &d2.cnt << endl; // 2. 类名 + 作用域限定符 cout << &Date::cnt << endl; cout << Date::cnt << endl; cout << Date::getCount() << endl; //普通成员只能通过对象访问,不能通过类名访问 d.Display(); //Date::Display(); //不支持 }

对于C++11的初始化方式,相对于给一个缺省值

private: int _year = 1; int _month = 1; int _day = 1;

02.友元函数

class Date { public: friend ostream& operator<<(ostream& outputS, Date& d); friend istream& operator >> (istream& inputS, Date& d); friend class B; } class B { public: //disPlay, fun, fun1都为Date类的的友元函数 void disPlay(const Date& d) { cout << d._year << d._month << d._day << endl; } void fun(const Date& d) { cout << d._year << d._month << d._day << endl; } void fun1(const Date& d) { cout << d._year << d._month << d._day << endl; } }; 输出流和输入流 ostream& operator<<(ostream& outputS, Date& d) { outputS << d._year << "-" << d._month << "-" << d._day << endl; return outputS; } istream& operator >> (istream& inputS, Date& d) { inputS >> d._year >> d._month >> d._day; return inputS; }

03.内部类

enum Color { BLACK, WHITE }; class C { public: class D { public: void fun(const C& c) { //可以通过外部类对象访问外部类的私有成员 cout << c._color << endl; cout << c._c << endl; cout << c._sc << endl; cout << C::_sc << endl; //可以直接访问外部类的static成员 cout << _sc << endl; } private: int _d; }; private: int _c; static int _sc; Color _color; //内部类可以在类的任何地方定义 class E { private: int _e; }; };

实践 日期类的实现

#include <iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1)//构造函数 { //在进行一个日期赋值前需要判断日期是否合法 if (year > 0 && month > 0 && month < 13 && day > 0 && day <= getMonthDay(year, month)) { _year = year; _month = month; _day = day; } else { cout << "日期不合法: " << year << "-" << month << "-" << day << endl; cout << "重置为默认值: 2000-1-1" << endl; _year = 2000; _month = 1; _day = 1; } } int getMonthDay(int year, int month)//获得当前月份的天数 { static int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; int day = days[month]; //如果是2月且为闰年,+1 if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))) ++day; return day; } // a += b Date& operator+=(int day) //加等操作 { // a += -b --> a -= b if (day < 0) return *this -= -day; //2020.5.1 + 20 --> 2020.5.21 //2020.5.21 + 20 --> 2020.5.41 --> 进位 --> -31 -->月份进位 --> 2020.6.10 //2020.12.6 + 90 --> 2020.12.96 --> 进位 --> -31 -->月份进位 --> 2020.13.65 -->年进位 // --> 2021.1.65--> --> 进位 --> -31 -->月份进位 --> 2021.2.34 --> --> 进位 --> -28 -->月份进位 --> 2021.3.6 _day += day; //判断日期是否溢出,溢出需要进位 while (_day > getMonthDay(_year, _month)) { //减去当月的天数,月份进位 _day -= getMonthDay(_year, _month); ++_month; //判断月份是否溢出 if (_month == 13) { //进位到下一年的1月 _month = 1; _year++; } } return *this; } //前置++: ++d: 首先++,返回++之后的值 Date& operator++() { return *this += 1; //return (*this).operator+=(1); //return *this; } //后置++: d++: 本身++, 返回++之前的值 //前置++, 后置++都为单目运算符 //如果为成员函数,则本质上不需要显式传参,编译器会自动传入this指针 //int: 形参不是一个真正的参数,只是一个标记参数,编译器看到这样的定义,通过语法树搜索,可以解释为后置++ Date operator++(int) { //保存++之前的值 Date ret(*this); //++ *this += 1; //返回++之前的值 return ret; } Date& operator-=(int day)//减等操作 { if (day < 0) return *this += -day; _day -= day; //判断_day是否为负值或者0, 退位 //2020.5.24 - 30 --> 2020.5.-6 --> 月份退位 --> +30 --> 2020.4.24 while(_day <= 0) { //月份退位 --_month; //月份是否为负值或者0 if (_month == 0) { _month = 12; //年份退位 --_year; } _day += getMonthDay(_year, _month); } return *this; } Date& operator--()//前置减减操作 { return *this -= 1; } Date operator--(int)//后置减减操作 { Date ret = *this; //Date ret(*this); *this -= 1; return ret; } // +, -运算符:不能修改操作数的内容 // c = a + b Date operator+(int day)//加运算符 { Date ret = *this; ret += day; return ret; } Date operator-(int day)//减运算符 { Date ret = *this; ret -= day; return ret; } bool operator==(const Date& date)//比较相等 { return _year == date._year && _month == date._month &&_day == date._day; } bool operator>(const Date& date)//大于 { if (_year > date._year) return true; else if (_year == date._year) { if (_month > date._month) return true; else if (_month == date._month) { if (_day > date._day) return true; } } return false; } bool operator>=(const Date& date)//大于等于 { return (*this > date) || (*this == date); } bool operator<(const Date& date) { return !(*this >= date); } bool operator<=(const Date& date) { return !(*this > date); } bool operator!=(const Date& date) { return !(*this == date); } void printD() // 等价于 printD(Date* const this) { cout << _year << " " << _month << " " << _day << endl; //可以修改内容 //this->_year = 100; //可以调用const成员函数 fun(); } //const成员函数中的const修饰的为第一个参数,即this指针 //const成员函数内部补能修改成员变量的值 void printD() const //等价于 printD(const Date* const this) { cout << _year << " " << _month << " " << _day << endl; //不能修改内容 //this->_year = 100; //不能调用非const成员函数,读写的权限不能被放大 //++*this; } void fun()const { } int operator-(Date& date)//两个日期进行相减 { Date d1(*this); Date d2(date); //d1 - d2 int num = 0; if (d1 > d2) { while (d1 > d2) { --d1; ++num; } return num; } else { //d1 <= d2 while (d1 < d2) { ++d1; ++num; } return -num; } } /*Date operator-(Date& date) { }*/ //取地址运算符重载函数: operator& //一般不需要显示定义,直接用默认即可 Date* operator&() { //return (Date*) 0x1234; return this; } const Date* operator&() const { //return nullptr; return this; } //private: int _year; int _month; int _day; }; ostream& operator<<(ostream& _cout, const Date& date) { _cout << date._year << " " << date._month << " " << date._day << endl; return _cout; }
最新回复(0)