文章目录
1 面向对象程序设计方法简介
首先需要理解什么是类?什么是对象?
类:将行为(对数据的操作)和状态(数据)打包在一起(并没有开空间)

对象:类的实例(占有实际的空间)(类实例化出对象)
对象具有两个要素:
- 属性(静态,表示状态)
- 行为(动态,表示类对象共同行为或功能)
简单理解,类相当于数据类型,对象相当于变量(数据类型的实例化)
C语言是面向过程程序设计,具有很多缺陷。
C++是面向对象程序设计,即将数据和对数据进行操作(输入、访问、修改、输出等)的函数绑定封装在一个称为类的数据类型中。
程序设计以数据为中心,程序由一组相互协作的对象组成。

面向对象程序设计具有信息隐藏、数据封装的特点
- 信息隐藏:处理某个数据的所有相关函数都集中在一起;隐藏方法实现细节(方法体),向外部提供公开接口(方法头),以供安全调用。
- 数据封装:数据在类这个封装体的外部不可能被访问;
2 类的概念
【类的概念】
-
类是将数据和操作封装在一起的复合数据类型
-
类有两类成员
- 一类是数据成员用于表示实体抽象的属性(不同类型的数据)
- 另一类是成员函数用来描述实体抽象的行为(处理这些数据的操作)
-
类作为一种数据类型在定义时不分配任何内存
-
类通过将数据和对数据进行操作的所有函数绑定在一起,实现了信息隐藏
-
在类中使用关键字public,private和protected对类的每一个成员(数据或函数)指定访问权限来实现数据封装
-
在类定义的{ }中的区域为类作用域,在作用域中说明的标示符只在类中起作用。
【定义类类型】
类类型的【定义语法】如下:
class 类名
{
private :
私有成员函数或数据成员
public :
公有成员函数或数据成员
protected :
被保护成员函数或数据成员
};//注意这里的分号;
【类定义常见的两种形式】
首先需要了解两个概念
-
类界面:包含类中数据成员和成员函数的函数原型
-
类实现:成员函数的具体实现
类定义常见的两种形式:一种是类界面和类实现分开;一种是不分开,类界面和类实现全在类定义中完成。
两者的区别:当不分开时,其成员函数将默认为内联函数(当函数被声明为内联函数之后, 编译器会将其内联展开, 而不是按通常的函数调用机制进行调用,当函数体比较小的时候内联函数可以提高性能,当函数体比较大时,滥用内联函数会导致程序变慢)。
【类定义形式1:类界面和类实现分开】。类实现在类外描述(当成员函数较大时采用)
类界面只给出属性和方法声明,比如定义CPerson类界面
class CPerson
{
private :
char name[20];
int age;
char sex;
public :
void setname(char *nameval); //set
void setage(int ageval);
int getage(); //get
};
在类外定义类中的函数成员的格式为:
<函数返回类型> <类名> ::<函数名> ( <形参>)
{
<函数体>
}
比如类外实现CPerson的方法
void CPerson::setname(char *nameval);
{
strcpy(name, nameval);
}
int CPerson::getage() //只有通过方法才能得到私有成员的值
{
return age;
}
【类定义形式2:类界面和类实现全在类定义中完成(当成员函数比较简单时)
类定义在定义中完成看这个例子就可以了。这里主要是set、get函数的书写。
//定义CPerson类,包含姓名、年龄、性别。
//特点:信息隐藏、数据封装
#include <iostream>
#include <cstring>
using namespace std;
class CPerson //类的界面
{
//private: 默认私有(实现了数据封装,外部不可访问private数据)(这部分的定义跟结构体是一致的)
char name[20];
int age;
char sex;
public: //跟结构体相比,多了函数成员
void setname(char* nameval) //设置属性(提供接口给外部进行修改)
{
strcpy(name, nameval);
}
void setage(int ageval)
{
age = ageval;
}
void setsex(char sexval)
{
sex = sexval;
}
void set(char* nameval, int ageval, char sexval)
{
strcpy(name, nameval);
age = ageval;
sex = sexval;
}
int getage() //访问属性(提供接口给外部访问)
{
return age;
}
char getsex()
{
return sex;
}
char* getname() //对字符串的访问,需要使用指针
{
return name;
}
void display()
{
cout << name << age << sex << endl;
}
}; //注意这里的分号
int main()
{
CPerson p; //对象(类类型实例化)
char name[20], sex;
int age;
cin >> name >> age >> sex; //输入前需要先定义,可以重复名称,因为此变量只对此函数有效
p.set(name, age, sex);
//cout<<p.name<<" "<<p.age<<" "<<p.sex<<endl; //error, private
cout << p.getname() << " " << p.getage() << " "; //调用对象的设置属性
if (p.getsex() == 'F')
cout << "Female" << endl;
else
cout << "Man" << endl;
}
3 对象的概念和定义方法
【类对象】
类实例/类对象/对象实例:数据类型为类的变量
类对象和一般变量一样,在定义时分配内存。(内存中存放着每个类对象的数据成员的值,对象的属性有各自空间,方法是否独立空间?后面会讲。)
类对象定义格式为:类名 类对象名列表 ;例: CPerson p1,p2;
【类成员的访问权限控制 】
- 公有成员:public成员,一般是成员函数,用于定义类的外部接口,在程序中的任何部分都可以访问。
- 私有成员:private成员,一般是数据成员,只能被类自身的成员函数访问。类成员在默认情况下是私有的。
- 保护成员:protected成员,可以被该类的成员函数、以及该类的直接或间接子类的成员函数所访问。(继承部分会学)

【对象成员的访问格式 】
- 类外访问
类外只能访问对象的公有数据成员。私有数据成员类外不可访问,但可通过公有函数接口访问。有两种访问方式,
- 用对象名访问
对象名.数据成员名/成员函数名(参数表) - 用对象指针访问
对象指针名->数据成员名/成员函数名(参数表)或者(*对象指针名).数据成员名/成员函数名(参数表)
例如:
CPerson p1, p2; //p1, 对象
CPerson* p = &p2; //对象指针*/
p1.set("wangwu", 19, 'm');
p->setname("limin");
p->setage(18);
p->setsex('F');
- 类内访问
- 访问自己的成员。一个类的成员函数可以访问该类所有数据。
格式:成员名(数据成员或函数成员) - 访问其它类对象成员。其它类类外,只能访问其公有成员。
格式:对象.成员名或对象指针->成员名 - 访问全局变量或函数。(记得调用全局函数前要在调用前添加声明)
格式:::变量名或函数名
总结下来就是这样的:(不包含友元、静态和继承)

4 同一类不同对象的存储组织
定义对象时,系统为每个对象分配存储空间。那如果定义了2个对象,是不是需要分别2个对象的数据和函数空间呢?
其实不是的。系统会用一段空间存放共同的函数代码段,在调用各对象的函数时,都去调用这个公用的函数代码。但是每个对象的属性就是各自维护。这样做会大大节约存储空间。
-
数据成员-- 各自分配存储空间。(每个对象维护自己的数据成员)
-
成员函数-- 共享同一代码副本,共享存储空间。(函数成员由同一类创建的所有对象共享)

5 this指针
如果说函数成员是共享的,那么不同对象调用同一成员函数,如何保证成员函数不会错误地访问另一对象?
C++通过为成员函数设置this指针来解决:
- C++编译对类的成员函数进行编译时,自动为每一个成员函数设置一个this指针。
- 该指针的功能是:当一个对象调用一个成员函数时,这个成员函数的this指针就指向调用这一个成员函数的对象。
- 这个指针是隐藏的,看不到。所以我们也可以按照之前一样使用成员函数的
成员数据名,或者也可以用this->数据成员名来表示目标对象的数据成员。
也就是说,CPerson的方法实现也可以写成:(不使用this没有影响,这里只是说明知识点)
char* CPerson::getname() //内联函数
{
return this->name;
//return name;
}
int CPerson::getage()
{
//return this->age;
return age;
}
6 const关键字修饰成员函数
把const关键字加到一个类的成员函数函数头参数列表的闭圆括号’)’与函数体的开花括号’{’之间,表示成员函数只使用数据成员的值,不修改数据成员的值。(后面我们会用到)
<返回数据类型> 函数名( ) const { 函数体}
<返回数据类型> 函数名( ) const;
程序设计人员通过将成员函数修饰为const的,使得其他人员无需查看成员函数的代码就可以知道该成员函数不会修改调用它的目标对象的状态,有助于信息隐藏和封装。
加const关键字的函数称为常函数。
7 对象的生存期
对象的生存期概念与变量生存期概念一样,由对象声明决定。其中各个数据成员的生存期由对象生存期决定,对象存在它就存在,对象被撤消它就被撤消。
- 局部对象:当对象被定义时,该对象被创建,当程序退出定义该对象所在的函数体或程序块时,释放该对象。
- 静态对象:当程序第一次执行所定义的静态对象时,该对象被创建,当程序结束时,该对象被释放。
- 全局对象:当程序开始时,创建该对象,当程序结束时释放该对象。
8 对象的初始化和对象的撤消
【初始化对象的数据成员】
我们说对象的数据成员反映了该对象的内部状态,那么如何初始化对象的数据成员?
因为类的封闭机制,类外的程序不能直接访问类中数据成员(私有的),所以也就不能直接在类外进行赋值。
可以用两种形式对对象中的数据成员进行初始化:
-
方式1 : 先声明对象,再调用对象中相关成员函数对其数据成员进行初始化(也就是我们前面所说的set函数)(这种方式比较麻烦)
-
方式2 : 类内成员初始化器,直接在类定义中初始化成员变量。 此方式使得各对象具有相同的初值(这种方式不符合实际)
class CPerson //CPerson类类型
{
private:
char name[100] = "qianjun" ;
int age = 20 ;
char sex = M ;
所以我们希望有一个方式3,能设立一种机制,使得在进行对象创建时,它就能自动对所创建的对象的数据成员进行初始化,这就是我们要讲的构造函数 。
8.1 构造函数(用来初始化)
【什么是构造函数】
- 构造函数是类的特殊成员函数,用于在定义类对象时对其数据成员进行初始化。
- 构造函数的名字必须与类名同名,参数根据需要可有可无。
- 不能为构造函数指定返回值类型,void也不可以。
- 构造函数应被声明为公有函数。
- 构造函数在创建类的对象时被系统自动调用,在程序的其他部分不能调用。
- 如果类声明时没有声明任何构造函数,则编译器会自动为其生成一个无参构造函数,生成对象时调用。如果类声明时声明了有参构造函数,则编译器不再为其提供无参构造函数。
- 与普通函数一样,构造函数也可以定义为内联函数,可以带默认参数,也可以重载。
例子: 对上述知识点的一个体现
#include<iostream>
#include<string>
using namespace std;
//类定义方式一
class CPerson;
void display(CPerson p); //调用全局函数需要先声明
class CPerson
{
private:
char name[100] ;
int age ;
char sex;
public:
// CPerson(const char* nameval="wangwu", int aval=18, char sval='F');
CPerson(const char* nameval, int aval, char sval); //构造函数也需要先声明
//CPerson() { cout << "default cons" << endl; }; //系统提供的默认的构造函数,空函数。当提供了有参构造后,就会调用这个提供的有参构造。
char* getname(); //内联函数
int getage();
char getsex();
void set(const char* nameval, int aval, char sval);
void setname(const char* nameval);
void setage(int aval);
void setsex(char sval);
void display();
}; //注意这里的分号
CPerson::CPerson(const char* nameval, int aval, char sval) //也是CPerson的函数成员,其实内容跟set函数是一样的
{
cout << this << "cons" << endl; //用来提示是否调用了这个构造函数
strcpy(name, nameval);
age = aval;
sex = sval;
}
void CPerson::set(const char* nameval, int aval, char sval)
{
strcpy_s(name, nameval);
age = aval;
sex = sval;
}
void CPerson::display() //类内函数display
{
//cout << name << " " << age << " " << sex << endl;
::display(*this); //加::表示调用了全局函数
}
void CPerson::setname(const char* nameval)
{
strcpy(name, nameval);
}
void CPerson::setage(int aval)
{
age = aval;
}
void CPerson::setsex(char sval)
{
sex = sval;
}
void display(CPerson p) //全局函数display
{
cout << p.getname() << " " << p.getage() << " " << p.getsex() << endl;
}
int main()
{
// CPerson p1;
char name[100], sex;
int age;
cin >> name >> age >> sex;
CPerson p1(name, age, sex); // 构造函数,直接初始化为输入的变量。直接调用。
p1.display();
return 0;
}
【构造函数的重载】
-
如果类声明时声明了有参构造函数,则编译器不再为其提供默认无参构造函数。
-
若定义不带参数的类对象,需要添加一个无参构造函数,空函数体。
-
CToolBooth() = default; 显式缺省,编译器使用系统默认的无参构造函数。不再需要提供空函数体。
class CPerson
{
char name[20];
int age;
char sex;
public:
CPerson() = default;
CPerson(const char* nval, int aval = 18, char sval=‘F’); // 构造函数重载,带默认参数
void setname(const char* nval);
void setage(int aval);
int getage();
};
int main()
{
CPerson s1; //调无参构造
CPerson s2("wangwu"); //调带参构造,默认两个参数
CPerson s3("zhaoliu", 20, 'M'); //调带参构造
s1.print();
s2.print();
s2.print();
}
【对象的动态建立与释放】
可以使用new和构造函数创建动态对象:
int main()
{
CPerson *s1 = new CPerson; //无参构造
CPerson *s2 = new CPerson("wangwu"); //带参构造,默认两个参数
CPerson *s3 = new CPerson("zhaoliu", 20, 'M'); //带参构造
s1->print();
s2->print();
s3->print();
delete s1;
delete s2;
delete s3;
}
【用参数初始化表对数据成员初始化】
也可以不在函数体内对数据成员初始化,而是在函数首部实现。但注意:这种写法仅限于构造函数。
//函数体内对数据成员初始化
CPerson::CPerson(const char* nval, int aval, char sval)
{
strcpy(name, nval);
age = aval;
sex = sval;
}
//函数首部实现数据成员初始化
CPerson::CPerson(const char* nval, int aval, char sval):age(aval),sex(sval)
{
strcpy(name, nval);
}
8.2 析构函数(用来释放和回收空间)
【什么是析构函数】
-
析构函数是类的另一特殊成员函数,用于在对象终止时由系统自动调用,以释放分配给对象的内存。
-
析构函数的函数名应为类名前加“~”。
-
析构函数没有参数,也不能指定返回值类型。
-
一个类只能声明一个析构函数。
-
析构函数也是公有的成员函数。
-
析构函数定义如下,都是系统自动调用的。如果类中没有声明,则编译器会自动提供一个带空函数体的析构函数。
CPerson::~CPerson()
{
cout << this << " des" << endl;
}
- 一般情况下,调用析构函数的次序正好与调用构造函数的次序相反: 最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用;而最后被调用的构造函数,其对应的析构函数最先被调用
关于构造函数和析构函数,只要弄清楚下面这个例子,就可以弄懂了,程序中注释得很清楚
#include <iostream>
#include <string>
using namespace std;
class CPerson
{
private:
string name;
int age ;
char sex ;
public:
CPerson():name("zhaoliu"),age(19),sex('M') //构造函数的初始化:也可以不在函数体内对数据成员初始化,而是在函数首部实现。(注意:这种写法仅限于构造函数。)
{
cout << this << " cons default" << endl; //调用的时候输出地址
} //定义无参构造函数。(是定义了有参构造函数之后,才需要重新定义无参构造函数。)
//CPerson() = default; //使用系统提供的无参构造,不需要提供任何函数体
CPerson(string nval, int aval = 20, int sval = 'F'); // 构造函数重载,带默认参数(直接赋予全部变量初始值的是无参构造函数),这里的navl变量还是需要传入的,所以仍是有参构造
CPerson(char* nval, int aval, int sval) //带参构造函数,其实是无参构造函数的重载
{
name = string(nval);
aval = age;
sex = sval;
}
void set(string nameval, int ageval, char sexval);//用set方法初始化
void display();
string getname();
int getage();
char getsex();
~CPerson(); //析构函数
};
CPerson::CPerson(string nval, int aval, int sval):name(nval),age(aval),sex(sval) //构造函数初始化方式二:函数首部实现数据成员初始化
{
/*this->name = nval; //构造函数初始化方式一:函数体内对数据成员初始化
this->age = aval;
this->sex = sval;*/
cout << this << " cons para" << endl; //调用的时候输出地址
}
CPerson::~CPerson() //对这个类来说,没有指针分配,所以不需要有函数体
{
cout << this << " des" << endl; //用来输出当前目标对象的地址,之前讲过的隐藏默认this指针
}
void CPerson::set(string nval, int ageval, char sexval)
{
this->name = nval;
//strcpy(name,nameval);
this->age = ageval;
this->sex = sexval;
}
void CPerson::display()
{
cout << name << " " << age << " " << sex << endl;
}
string CPerson::getname()
{
return name;
}
int CPerson::getage()
{
return age;
}
char CPerson::getsex()
{
return sex;
}
int main()
{
string name;
int age;
char sex;
//输入:liming 20 F
cin >> name >> age >> sex;
//调用了无参构造,输出:0x7ff7bc9ab4e8 cons default
CPerson p2; //调无参构造函数,定义对象的时候一定会调用构造函数的,这里调用的是系统默认的无参构造函数CPerson()。
//输出p2对象的地址 输出:p2 address: 0x7ff7bc9ab4e8
cout << "p2 address: " << &p2 << endl;
//使用了set函数初始化(这里其实无参构造默认是zhaoliu,在这里使用了set再次初始化为liming)
p2.set(name, age, sex);
//对p2对象进行了打印,输出:liming 20 F
p2.display();
//输入:wangwu 19 M
cin >> name >> age >> sex;
//调用了带参数构造函数 输出:0x7ff7bc9ab4b0 cons para
CPerson p1(name,age,sex); //调带参构造,定义对象的同时初始化。当定义了带参的构造函数后,就不会再使用系统默认的无参构造函数了
//输出p1对象地址 输出:p1 address: 0x7ff7bc9ab4b0
cout << "p1 address: " << &p1 << endl;
//对p1对象进行打印 输出:wangwu 19 M
p1.display();
//CPerson p3("wangwu"); //调带参构造,默认两个参数
//对象的动态建立,调用了无参构造,输出:0x600002a6c000 cons default
CPerson* p = new CPerson;
//无参构造已经有默认初始化了,输出:zhaoliu 19 M
p->display();
//释放内存,注意:如果没有delete,就不会调用析构函数
delete p;
//这里一共建立了三个对象,分别是p2,p1,p,程序运行结束后会调用析构函数,释放空间,分别输出
//0x600002a6c000 des (释放p的地址)
//0x7ff7b45ae4a0 des (释放p1的地址)
//0x7ff7b45ae4d8 des (释放p2的地址)
//可以看到,最后被调用的构造函数,其对应的析构函数最先被调用(跟栈一样,后进先出)
}
/* 输入数据
liming 20 F
wangwu 19 M
*/
8.3 拷贝构造函数
【什么是拷贝构造函数】
- 拷贝构造函数(复制构造函数),是类的一个公有成员函数。
- 拷贝构造函数名与构造函数相同,但只有一个参数,即同类的一个对象的引用。
CPerson() = default; //缺省构造
CPerson(const char* nval, int aval = 18, char sval = 'F'); //带参构造
CPerson(const CPerson& rhs); //拷贝构造,用rhs对象初始化当前对象
//--------
CPerson::CPerson(const CPerson& rhs)
{
strcpy(name, rhs.name);
age = rhs.age;
sex = rhs.sex;
}
//--------
CPerson s1; //调无参构造
CPerson s2("wangwu"); //调带参构造,默认两个参数
CPerson s3("zhaoliu", 20, 'M'); //调带参构造
CPerson s4(s3); //调拷贝构造
CPerson s5 = s4; //调拷贝构造
- 在创建对象时,当使用一个已知的对象来初始化另一个对象(复制对象)时,系统会自动隐式地调用拷贝构造函数。(这个是浅拷贝,使用的是共享空间)。这个缺省的拷贝构造函数,将源对象中的数据成员值逐个拷贝到目标对象中(memcpy)。
- 如果使用系统默认的拷贝构造可以不写,或使用=default显式缺省
【浅拷贝】
- **浅拷贝:**简单地将作为实参的对象的每个数据成员复制给新声明的对象的相应数据成员。浅拷贝使用的是共享空间(如用对象s构造对象s1,那么s和s1使用的是同一个地址空间)。(系统提供的缺省拷贝构造函数就是使用浅拷贝。)
- 当浅拷贝遇到指针时:缺省的拷贝构造函数,完成数据成员的一一赋值,因为指针共享空间,析构会释放两次空间,导致程序出错。
【深拷贝】
- 深拷贝:为对象中的指针成员申请必要的内存,在成功获得内存空间后,再把被复制对象的指针所指向的内存中的值拷贝到新申请的内存中。(相当于先给s1 new一个空间,然后再用s构造s1,这样s个s1就是独立空间,不会影响后面析构空间释放了)
- 当类的数据成员中包含指针时,应该自定义拷贝构造函数,实现深拷贝,以避免虚指针的出现。
例子:建立一个栈类。通过这个理解浅拷贝和深拷贝
属性:数据空间,栈顶。
方法:初始化;元素入栈; 元素出栈;获得栈顶元素;栈空;栈满;栈元素数等。
只在一端进行操作。下图为栈的数组存储示意图。栈顶,top=0。对于栈的操作,要理解下面这个图。

#include <iostream>
using namespace std;
#define SIZE 100
//栈类
class cstack
{
//int data[SIZE]; //静态数组,后面定义对象的时候,调用的是系统默认的拷贝构造函数
int* data; //使用指针进行动态分配,这个时候需要定义一个拷贝构造函数
int itop; //top 栈顶指向的空间(有多少个元素),初始化为空
public:
//空栈 无参构造
cstack() :itop(0)
{
data = new int[SIZE]; //new一个空间
}
/*cstack(const cstack& rhs) //系统提供的拷贝构造函数,浅拷贝,完成数据成员的一对一赋值,相当是一个无参构造
{
//整体思想
this->itop = rhs.itop; //当前栈顶等于对象的栈顶,一个一个拷贝过来
//静态数组的实现方式
memcpy(this->data, rhs.data); //方式一:因为这里是连续地址,也可以使用memcpy进行拷贝
for (int i = 0; i < this->itop; i++) //方式二:因为这里是连续地址,也可以直接for循环
data[i] = rhs.data[i];
//动态数组的实现方式,也就是两个指针相同,指向的是同一个地址
data = rhs.data;
}*/
cstack(const cstack &rhs) //数据成员有指针,涉及已有对象备份新对象,自己写个深拷贝,注意传的是引用
{
data = new int[SIZE]; //new一个空间给新对象
this->itop = rhs.itop;
memcpy(this->data, rhs.data, sizeof(int)*SIZE);
}
//入栈,当前栈顶指向的空间先写入元素,itop+1
void push(int e)
{
data[itop++] = e;
}
//返回栈顶元素,因为itop指向的空间没有元素。-1就是栈顶元素
int top()
{
return data[itop - 1];
}
//出栈。不用操作,itop-1
void pop()
{
itop--;
}
//判定栈空
bool empty()
{
return itop == 0;
}
//判定栈满
bool full()
{
return itop == SIZE;
}
//输出,方便查看
void print()
{
cout << data << endl; // 输出对象的数据地址,
cout << itop << endl; // 输出itop,栈顶指向的空间(有多少个元素)
for (int i = 0; i < itop; i++) //循环输出data的值
cout << data[i] << " ";
cout << endl;
}
//获得栈当前的大小
int size()
{
return itop;
}
//析构函数,
~cstack()
{
//首先析构掉s1 输出:0x7ff7b74654d0 0x7fb1d8104080 (s1的this,s1的data)
//接着析构掉s:输出:0x7ff7b74654f8 0x7fb1d8004080 (s的this,s的data)
//可以看到上面s1和s的data是同一个地址,如果使用的系统默认的浅拷贝构造函数,s1和s会是共享空间,析构就会导致同一个地址释放了两次,就会出错
//所以需要自己增加写一个深拷贝,也就是给新开的s1分配一个新的空间,后面用s来构造s1的时候,就可以将s的data拷贝到s1的空间,而不是共享空间
cout << this << " " << data << endl;
delete []data; //因为是动态分配空间,析构函数需要将空间delete掉
}
};
int main()
{
cstack s;
int e, n;
//输入:5 表示5个数
cin >> n;
for (int i = 0; i < n; i++)
{
//分别输入这5个数,并push进栈 输入:1 2 3 4 5
cin >> e;
s.push(e);
}
cstack s1(s); //拷贝构造 用栈对象s构建新的栈对象s1(已有对象构造新对象,相当于拷贝)
//输出s对象的data空间,输出:0x7fc65ff04420
s.print();
//输出s1对象的data空间 输出:0x7fc660804080
s1.print();
cout << "----------" << endl;
while (!s.empty()) //s不断出栈
{
//每pop一次,用top()返回栈顶元素,因为itop指向的空间没有元素。-1就是栈顶元素
cout << s.top() << " " << endl;
s.pop(); //出栈。不用操作,itop--
}
cout << "----------" << endl; //s1不断出栈
while (!s1.empty())
{
cout << s1.top() << " " << endl;
s1.pop();
}
}
/*
5
1 2 3 4 5
*/
【拷贝构造函数的调用情况】
- 当使用一个已有的对象初始化建立新对象时
C++ CPerson s4(s3) 或 CPerson s4 = s3; - 当函数的参数为类的对象,
void print(CPerson s)。(调用时将实参对象传递给形参时,需要建立一个实参副本,系统调用拷贝构造函数来实现) - 当函数的返回值是类的对象
CPerson input()。系统将建立TollBooth的一个副本,将它返回给主调函数。
【返回对象与返回引用】(待补充)
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << this << " constructor" << endl;
}
A(const A& a)
{
cout << this << " copy constructor\n" ;
}
~A()
{
cout << this << " destructor\n";
}
};
A fun1(A a) {
cout << &a << " return object" << endl;
return a;
}
A& fun2(A& a) {
cout << &a << " return reference" << endl;
return a;
}
int main()
{
A a;
cout << "-----------" << endl;
fun1(a);
cout << "-----------" << endl;
fun2(a);
cout << "-----------" << endl;
}
// 结论:A& fun2(A& a)比A fun1(A a)效率高。
9 复合类
复合类是指:以其他类的对象(不能是复合类本身的对象)作为其数据成员或成员函数的参数的类。
这里涉及的主要问题是:如何对类中的包含的对象成员进行初始化?
不能在类定义中直接调用对象成员对应的类的构造函数来进行初始化。主要有两种初始化方式:
- 方式一:在类的构造函数的定义(初始化列表)中分别引用对象成员所属类的构造函数,格式如下:
类名::类名(形参表):对象1(形参表),...,对象n(形参表)
{
函数体
}
- 方式二:使用拷贝构造
类的构造函数调用对象成员构造函数的次序 :依类定义中对象成员出现的先后次序
析构函数调用次序恰好与构造函数调用次序相反:先构造的函数后析构。
复合类通过下面这道例题进行理解,涉及到的知识点都注释得很清楚。
定义点类CPoint。属性:x,y坐标。
定义圆类CCircle,属性:点对象圆心,半径。
定义圆柱体类CCylinder,属性:2个圆,半径,高。
注意复合类构造函数的用法和调用顺序。
#include <iostream>
using namespace std;
//点类CPoint。属性:x,y坐标。
class cpoint
{
private:
int x, y;
public:
//无参构造
cpoint()
{
cout << this << "point cons default" << endl;
}
//有参构造,只有构造函数才可以这么定义
cpoint(const int xval, const int yval) : x(xval), y(yval)
{
cout << this << " point cons para" << endl;
}
int getx() const { return x; }
int gety() const { return y; }
void set(int xval, int yval)
{
x = xval;
y = yval;
}
//析构函数
~cpoint()
{
cout << this << " " << x << " " << y << " point des" << endl;
}
};
//圆类CCircle,属性:点对象圆心,半径。这就是一个复合类了,使用了点类
class ccircle
{
int r;
cpoint c; //点类c
public:
//无参构造
ccircle()
{
cout << this << " circle cons default" << endl;
}
//有参构造
ccircle(int x, int y, int rval) : c(x, y), r(rval) //这句是重点,传递复合类,这里使用c(x,y)表示输入点类,这种方式调用了点类的带参构造函数
{
// c.set(x,y); //点类私有成员不可访问,可通过set进行输入,这种方式会调用点类的无参构造函数
cout << this << " circle cons para" << endl;
}
//析构函数
~ccircle()
{
cout << this << " " << c.getx() << " " << c.gety() << " ccircle des" << endl;
}
//常函数,进行输出。表明成员函数只使用对象数据成员的值,不修改值
void print() const
{
cout << c.getx() << " " << c.gety() << " " << r << endl;
}
};
//圆柱体类CCylinder,属性:2个圆,半径,高。
class ccylinder
{
//多个对象,构造顺序按照定义顺序
ccircle upc; //上圆
ccircle downc; //下圆
int h; //高
public:
//无参构造
ccylinder()
{
cout << "ccylinder cons default" << endl;
}
//带参构造,调用圆类(复合类),调用方式一::对象1(形参表),...,对象n(形参表)
ccylinder(int x1, int y1, int x2, int y2, int r, int hval) : downc(x1, y1, r), upc(x2, y2, r), h(hval)
{
// downc.set
cout << this << " ccylinder cons para" << endl;
}
//调用方式二:拷贝构造,传入自身的三个数据对象
ccylinder(const ccylinder &c) : upc(c.upc), downc(c.downc), h(c.h)
{
cout << this << " ccyclinder copy cons" << endl;
}
void print()
{
downc.print();
upc.print();
cout << h << endl;
}
//析构函数
~ccylinder()
{
cout << this << " ccylinder des" << endl;
}
};
int main()
{
int x1, y1, x2, y2, r, h;
//输入:1 2 1 2 10 3
cin >> x1 >> y1 >> x2 >> y2 >> r >> h;
/*
先构建上圆(先构建点类,再构建圆类)输出:
0x7ff7b02ff4ec point cons para
0x7ff7b02ff4e8 circle cons para
接着构建下圆(先构建点类,再构建圆类)输出:
0x7ff7b02ff4f8 point cons para
0x7ff7b02ff4f4 circle cons para
接着构建圆柱,输出:
0x7ff7b02ff4e8 ccylinder cons para
*/
ccylinder cy(x1, y1, x2, y2, r, h);
// 调用拷贝构造,输出:0x7ff7b02ff4c8 ccyclinder copy cons
// 因为是拷贝构造,所以看不到点类和圆类的构造过程,但是可以看到析构过程
ccylinder cybak(cy);
//调用圆柱类的print函数,输出:1 2 10 / 1 2 10 / 3
cybak.print();
/* 调用各个析构函数
方式二的析构:
先释放拷贝构造 cybak
0x7ff7b02ff4c8 ccylinder des
再释放拷贝构造 的圆类和点类
0x7ff7b02ff4d4 1 2 ccircle des
0x7ff7b02ff4d8 1 2 point des
0x7ff7b02ff4c8 1 2 ccircle des
0x7ff7b02ff4cc 1 2 point des
---------------------------
方式一的析构:
调用圆柱类的析构函数
0x7ff7b02ff4e8 ccylinder des
调用下圆的析构函数
0x7ff7b02ff4f4 1 2 ccircle des
0x7ff7b02ff4f8 1 2 point des
调用上圆的析构函数
0x7ff7b02ff4e8 1 2 ccircle des
0x7ff7b02ff4ec 1 2 point des
*/
}
/*
//用来验证复合类中的构造和析构关系
int main()
{
int x1,y1,r;
// 输入1 1 2
cin >> x1 >> y1 >> r;
// 圆类对象初始化,先调用了点类的带参构造函数,输出:0x7ff7b8a1d50c point cons para
// 再调用了圆类的带参构造函数,输出:0x7ff7b8a1d508 circle cons para
ccircle c(x1,y1,r);
// 圆类对象打印,输出:1 1 2
c.print();
// 先调用圆类析构函数,输出:0x7ff7b8a1d508 1 1 ccircle des
// 再调用点类析构函数,输出:0x7ff7b8a1d50c 1 1 point des
}
*/
// 1 2 1 2 10 3
10 对象数组初始化
【静态对象数组初始化】
假设已定义类cpoint,静态对象数组的初始化跟普通数组是一样的
cpoint p[2] = {{10,20},{20,30}};
cpoint p[2] = {cpoint(10,30),cpoint(20,30)};
【动态对象数组初始化】
动态对象数组的初始化有三种方式:
//方法一:使用new
cpoint *p = new cpoint[2]{
cpoint(10,20),cpoint(20,30)};
//方法二:用一个临时对象,在 vector 里拷贝构造所有对象
vector<cpoint> p(2,cpoint(10,20))
//方法三:先为数组分配空间,然后使用 “placement new”在这块内存上构造对象
cpoint *p = (cpoint *)operator new[](3 * sizeof(cpoint));
for (int i = 0; i < 3; i++)
{
cin >> x >> y;
new(&p[i]) cpoint(x,y); // 有参构造函数构造
}
for(int i=0; i<3; i++)
p[i].~cpoint(); //析构对象
operator delete(p); //释放空间
本文深入探讨了面向对象编程的基础,包括类和对象的概念,对象的初始化与撤销,构造函数和析构函数的作用。详细解释了构造函数的重载、拷贝构造函数以及对象的存储组织,强调了信息隐藏和数据封装的重要性。此外,还介绍了成员函数的this指针以及const关键字在成员函数中的应用。
665

被折叠的 条评论
为什么被折叠?



