目录
一、类
【1】类
【2】应用实例
练习:
【3】封装
二、this指针
【1】this指针的格式
【2】必须使用this指针的场合
三、类中的特殊成员函数
【1】构造函数
i)功能
ii)格式
iii)构造函数的调用时机
iv)构造函数允许函数重载
v)构造函数的初始化列表
1.格式:
2.必须使用初始化列表的情况:
2】析构函数
i)功能
ii)调用时机
iii)格式
iv)需要显性定义出析构函数的情况
【3】拷贝构造函数
i)功能
ii)格式
iii)调用时机
iv)深浅拷贝问题
【4】拷贝赋值函数
i)功能
ii)格式
作业:
一、类
面向对象的三大特征:封装、继承和多态
C++中封装是由类来实现的
C++中的类是由结构体演变而来的
结构体:变量和函数
类:成员属性(变量)、成员方法(函数)
【1】类
类中成员默认是private权限
class 类名
{
public: ---->类外和类内都可以访问
公有的成员变量/函数
private: ---->类内可以访问,类外不能访问
私有的成员变量/函数
protected:
受保护的成员变量/函数
};
【2】应用实例
#include <iostream>
using namespace std;
class Person
{
private:
string name;
public:
int age;
int high;
void set_name(string n); //在类内声明函数
void show()
{
cout << "name = "<< name << " age = "<<age << " high = "<<high << endl;
}
};
int main()
{
Person p1; //实例化一个类对象p1
p1.set_name("zhangsan");
p1.age = 90;
p1.high = 100;
p1.show();
return 0;
}
//类外实现
void Person::set_name(string n)
{
name = n;
}
练习:
1、定义一个矩形类(Rectangle),包含私有成员长(length)、宽(width),定义成员函数:
设置长度: void set_len(int l);
设置宽度: void set_wid(int w);
获取长度: int get_len();
获取宽度: int get_wid();
显示周长和面积: void show();
#include <iostream>
using namespace std;
class Rec
{
int length;
int width;
public:
//所有函数的类内声明
void set_len(int l);
void set_wid(int w);
int get_len();
int get_wid();
void show();
};
void Rec::set_len(int l)
{
length = l;
}
void Rec::set_wid(int w)
{
width = w;
}
int Rec::get_len()
{
return length;
}
int Rec::get_wid()
{
return width;
}
void Rec::show()
{
cout << "周长" << 2*(length+width) << endl;
cout << "面积" << length*width << endl;
}
int main()
{
Rec r1; //实例化了一个Rec类的类对象r1
r1.set_len(10);
r1.set_wid(3);
cout << "长:" << r1.get_len() << endl;
cout << "宽:" << r1.get_wid() << endl;
r1.show();
return 0;
}
【3】封装
- 把某一事物的所有属性(变量)、行为(成员方法/成员函数),封装成一个整体,给私有的属性,提供公有的接口,以便于用户修改
- 类中成员都有访问权限(public,private,protected)
- 访问权限,是对于整个类而言的
- 类中同一访问权限,可以可以出现多次
- 类中默认是私有权限
二、this指针
this指针,是类内默认提供给非静态成员函数的指针,this指向,指向类对象本身,哪个类对象调用函数,this就指向哪一个类对象(谁调用函数this就指向谁)
【1】this指针的格式
类名 *const this; ----->不能修改指针指向
【2】必须使用this指针的场合
- 当成员函数的参数和类中成员属性同名时,需要使用this指向表明哪一个是类中的成员属性
- 拷贝赋值函数中,需要返回自身的引用,也需要使用this指针
#include <iostream>
using namespace std;
class Rec
{
int length;
int width;
public:
//所有函数的类内声明
void set_len(int length);
void set_wid(int width);
int get_len();
int get_wid();
void show();
};
void Rec::set_len(int length)
{
this->length = length;
}
void Rec::set_wid(int width)
{
this->width = width;
}
int Rec::get_len()
{
return length;
}
int Rec::get_wid()
{
return width;
}
void Rec::show()
{
cout << "周长" << 2*(length+width) << endl;
cout << "面积" << length*width << endl;
}
int main()
{
Rec r1; //实例化了一个Rec类的类对象r1
Rec r2;
r2.set_len(20);
r1.set_len(10);
r1.set_wid(3);
cout << "长:" << r1.get_len() << endl;
cout << "宽:" << r1.get_wid() << endl;
r1.show();
return 0;
}
三、类中的特殊成员函数
类中的特殊成员函数,程序员如果不手动写出来,系统会默认提供
一共有6个特殊的成员函数:构造函数、析构函数、拷贝构造函数、拷贝赋值函数,取地址运算符的重载和常取地址运算符的重复,C++11又提供了:移动构造、移动赋值函数
本文只有前四个
【1】构造函数
用于实例化类对象,系统会自动调用,系统自动提供的是无参构造
如果手动定义了构造函数,那么系统不会再提供一个无参构造
i)功能
实例化类对象时,申请空间使用的
ii)格式
类名(参数)
{
函数体;
}
iii)构造函数的调用时机
栈区:什么时候实例化类对象,什么时候调用:
在 C++ 中,对象可以在栈区实例化,这也被称为自动存储类对象。实例化对象时,会在栈上为对象分配内存空间,并调用对应的构造函数进行初始化。
对象的实例化和调用可以发生在不同的情况下:1.在函数内部:当在函数内部定义一个类对象时,对象的实例化和构造函数的调用将发生在函数的作用域内。例如:
void foo() { MyClass obj; // 实例化对象并调用构造函数 // 函数内的其他操作 } // 函数结束,对象超出作用域,析构函数将会被调用
2.作为类的成员:如果一个类定义在另一个类中,并作为成员变量,那么对象的实例化和构造函数的调用将随着父类对象的实例化而发生。例如:class ParentClass { public: MyClass obj; // 对象作为成员变量 }; int main() { ParentClass parentObj; // 实例化父类对象,对象的实例化和构造函数调用都会发生 // 其他操作 } // 父类对象超出作用域,析构函数将会被调用,对象的析构顺序与构造相反
需要注意的是,在栈上实例化的对象会在其所在的作用域结束时超出作用域,从而自动调用析构函数,释放对象占用的内存空间。这种自动释放内存的方式称为自动存储管理。
堆区:定义类的指针时,不调用构造函数。
什么时候new,什么时候调用 :应该在对象不再使用时调用对应的析构函数来释放内存,以避免内存泄漏。
iv)构造函数允许函数重载
- 系统默认提供一个无参构造函数
- 可以显性定义出有参构造,但是此刻系统不再提供默认的无参构造,还可以显性写出无参构造
- 构造函数是允许函数重载的
#include <iostream>
using namespace std;
class Stu
{
string name;
int age;
public:
//定义Stu类的无参构造
Stu()
{
cout << "Stu的无参构造" << endl;
}
//函数重载
Stu(string name,int age)
{
this->name = name;
this->age = age;
cout << "Stu的有参构造" << endl;
}
Stu(string name)
{
this->name = name;
cout << "Stu的对name的有参构造" << endl;
}
};
int main()
{
Stu s1("zhangsan",18); //栈区的类对象
Stu *p; //在栈区申请一个Stu*类型的指针
p = new Stu("lisi"); //在堆区申请一个Stu的空间,会调用构造函数
return 0;
}
v)构造函数的初始化列表
1.格式:
类型(形参):成员属性1(形参1),成员属性2(形参2),······{函数体内容}
构造函数提供了初始化列表的机制,可以直接在函数体外执行初始化操作
初始化列表由:引出,每一个成员属性的值之间以,逗号分隔
2.必须使用初始化列表的情况:
- 形参和成员属性同名
- 类中有引用成员时,需要使用初始化列表
- 类中有const修饰的成员时
- 一个类中有另一个类的子对象时,需要使用初始化列表(如果两个类都有无参构造,是不需要写初始化列表)
//包含其他类的子对象
#include <iostream>
using namespace std;
class Per
{
string name;
public:
//Per提供的有参构造
Per(string name)
{
this->name = name;
cout << "Per的有参构造" << endl;
}
Per()
{
cout << "Per的无参构造" << endl;
}
//一个show函数,输出name
void show()
{
cout << "Per中的name=" << name << endl;
}
};
class Stu
{
int score;
public:
Per p1; //Stu类中,包含另一个类的子对象,且另一个类只有有参构造函数
public:
Stu(string name):p1(name) //必须使用初始化列表,并且在初始化列表显性调用另一个类的构造函数
{
cout << "Stu的有参构造" << endl;
}
Stu()
{
cout << "Stu的无参构造" << endl;
}
};
int main()
{
Stu s1("zhangsan");
//通过s1这个类对象,调用Per中的show函数
s1.p1.show();
Stu s2;
return 0;
}
//同名或者是包含引用成员/const修饰的成员
class Stu
{
string name;
//int &age; //类中有引用成员
const int score;
public:
//定义Stu类的无参构造
// Stu()
// {
// cout << "Stu的无参构造" << endl;
// }
//函数重载,使用初始化列表的方式
//初始化列表也可以解决形参和成员属性同名问题
Stu(string name,int age):name(name),score(age) //在函数体外给成员属性初始化
{
cout << "Stu的有参构造" << endl;
}
void show()
{
cout << name << " " << score << endl;
}
};
int main()
{
Stu s1("zhangsan",18); //栈区的类对象
s1.show();
Stu *p; //在栈区申请一个Stu*类型的指针
p = new Stu("lisi",20); //在堆区申请一个Stu的空间,会调用构造函数
return 0;
}
2】析构函数
析构函数,不支持函数重载,并且析构函数只有:~类名(){}
不需要传参数,public权限
i)功能
回收类对象的空间
ii)调用时机
栈区:类对象消亡时,自动调用
堆区:什么时候delete,什么时候调用析构函数
先构造的后析构,后构造的先析构
iii)格式
~类名()
{
函数体;
}
iv)需要显性定义出析构函数的情况
- 类中有指针成员,并且指针成员指向堆区的空间
- 如果直接调用普通的析构函数,类对象消亡后,没有办法再去释放掉堆区的空间,会造成内存泄漏
#include <iostream>
using namespace std;
class Stu
{
int *p;
public:
Stu():p(new int) //在不传参的情况下,给指针成员p用堆区的空间初始化
{
cout << "Stu无参构造" << endl;
}
Stu(int a):p(new int(a)) //在参数为整形变量的情况下,让p指向堆区申请的空间
//但是把这篇空间的内容,用传过来的整形变量初始化
{
cout << "Stu的有参构造" << endl;
}
Stu(int *p) //要求传过来的指针的值,一定是指向堆区空间
{
this->p = p;
cout << "Stu传指针的有参构造" << endl;
}
~Stu()
{
//在析构之前释放堆区的空间
cout << "准备释放空间:" << p << endl;
delete p;
p = nullptr;
cout << "Stu的析构函数" << endl;
}
};
int main()
{
Stu s1(90);
return 0;
}
【3】拷贝构造函数
i)功能
使用已有的类对象,给类对象初始化的时候,自动调用拷贝构造函数
ii)格式
类名(类名 &other)
{
函数体的内容;
}
拷贝构造函数,是一个构造函数,
函数名:类名
形参:其他类对象的引用
iii)调用时机
- 使用已有的类对象给新的类对象初始化
- 当函数的形参是一个类对象时,也会调用拷贝构造函数
- 当函数的返回值是一个类对象时,也会调用拷贝构造函数
#include <iostream>
using namespace std;
class Stu
{
string name;
public:
Stu()
{
cout << "Stu的无参构造" << endl;
}
Stu(string name)
{
this->name = name;
cout << "Stu的有参构造" << endl;
}
Stu(Stu &other) //Stu的拷贝构造函数,和无参构造以及有参构造构成函数冲澡
{
this->name = other.name;
cout << "Stu的拷贝构造函数" << endl;
}
void show()
{
cout << name << endl;
}
};
Stu fun(Stu s1) //定义了一个函数fun,形参是一个类对象
{
cout << "调用成功" << endl;
return s1;
}
int main()
{
Stu s1("zhangsan");
cout << "s1:";
s1.show();
Stu s2 = s1; //拷贝构造函数
cout << "s2:";
s2.show();
fun(s2);
return 0;
}
iv)深浅拷贝问题
什么情况下发生:类中存在指针成员时
深拷贝:两个指针成员指向不同空间,但是取*的结果是一致
浅拷贝:两个指针成员指向同一片空间
1、对同一片空间二次释放问题
2、两个类对象使用同一片空间,发生资源抢占问题
#include <iostream>
using namespace std;
class Stu
{
int *p;
public:
Stu():p(new int) //在不传参的情况下,给指针成员p用堆区的空间初始化
{
cout << "Stu无参构造" << endl;
}
Stu(int a):p(new int(a)) //在参数为整形变量的情况下,让p指向堆区申请的空间
//但是把这篇空间的内容,用传过来的整形变量初始化
{
cout << "Stu的有参构造" << endl;
}
Stu(int *p) //要求传过来的指针的值,一定是指向堆区空间
{
this->p = p;
cout << "Stu传指针的有参构造" << endl;
}
~Stu()
{
//在析构之前释放堆区的空间
cout << "准备释放空间:" << p << endl;
delete p;
p = nullptr;
cout << "Stu的析构函数" << endl;
}
void show()
{
cout << "p=" << p << endl;
}
void set_p(int a)
{
*p = a; //通过指针变量p,修改内存中的值
}
void show_p()
{
cout << *p << endl; //通过指针变量,输出p指向的内存中的值
}
//拷贝构造函数
Stu(Stu &other)
{
p = new int; //手动让s2的指针p指向堆区的空间
//实现深拷贝
*p = *(other.p);
cout << "Stu的拷贝构造函数" << endl;
}
};
int main()
{
Stu s1(90); //会给s1的指针成员在堆区申请一片空间使用90初始化
Stu s2 = s1; //申请了一个Stu的类对象的空间,也实现了用s1的值初始化s2
//左调右参
//上面一行,调用了拷贝构造函数
//使用了默认提供的拷贝构造,会造成指针成员,指向同一片空间的问题
// cout << "s1:";
// s1.show();
// cout << "s2:";
// s2.show();
s1.show_p();
s2.show_p();
s1.set_p(101);
s1.show_p();
s2.show_p();
return 0;
}
【4】拷贝赋值函数
i)功能
使用已有的类对象,给已有的类对象赋值的时候,会调用拷贝赋值函数。
ii)格式
类名 &operator=(const 类名 &other)
{
函数体;
}
作业:
设计一个Per类,类中包含私有成员:姓名、年龄、指针成员身高、体重,再设计一个Stu类,类中包含私有成员:成绩、Per类对象 p1,设计这两个类的构造函数、析构函数和拷贝构造函数。
#include <iostream>
using namespace std;
class Per{
string name;
int age;
float* height;
float* weight;
public:
//构造函数
Per(string name,int age,float* height,float* weight)
{
this->name = name;
this->age = age;
this->height = height;
this->weight = weight;
cout << "Per的构造函数" << endl;
}
//析构函数
~Per()
{
delete height;
delete weight;
height = nullptr;
weight = nullptr;
cout << "Per的析构函数" << endl;
}
//拷贝构造函数
Per(Per& other)
{
this->name = other.name;
this->age = other.age;
height = new float;
*height = *(other.height);
weight = new float;
*weight = *(other.weight);
cout << "Per的拷贝构造函数" << endl;
}
void show()
{
cout << name << endl;
}
};
class Stu{
float score;
Per p1;
public:
//必须使用初始化列表的情况:
//形参和成员属性同名
//类中有引用成员时,需要使用初始化列表
//类中有const修饰的成员时
//一个类中有另一个类的子对象时,需要使用初始化列表(如果两个类都有无参构造,是不需要写初始化列表)
//Stu构造函数
Stu(float score,string name,int age,float* height,float* weight):score(score),p1(name,age,height,weight)
{
cout<< "Stu的构造函数" << endl;
}
//析构函数
~Stu()
{
cout << "Stu的析构函数" << endl;
}
//拷贝构造函数
Stu(Stu& other):score(other.score),p1(other.p1)
{
cout << "Stu的拷贝构造函数" << endl;
}
};
int main()
{
float height = 175;
float weight = 60;
Per p("zhangsan",18,&height,&weight);
Per p1 = p;
Stu stu1(85.5,"lisi",18,&height,&weight);
Stu stu2 = stu1;
p.show();
return 0;
}