一、封装
1.1 面向对象的三大特质
封装、继承、多态,如果问有四大特征,可以外加一个抽象
封装:将实现同一事物的所有的属性(成员变量)和行为(成员函数)封装到一个整体,我们称之为类(class)。类对外部信息有一个屏障作用。
1.2 C++中的类(class)
C++中的类,是由C++中的结构体演化而来的,只需要将struct改成关键字class,就定义了一个类
C++中类和结构体的区别:默认的权限不同,结构体中默认权限为public,类中默认权限为private
默认的继承方式不同,结构体的默认继承方式为public,类的默认继承方式为private
1.3 定义格式
class 类名
{
public:
功能的成员属性、函数
protected:
受保护的成员属性、函数
private:
私有的成员属性、函数
};
1> 类中的成员属性和成员函数分为不同的权限
public:该权限下的成员,可以在类内、子类中、类外被访问
protected:该权限下的成员,可以在类内、子类中直接被访问,类外不允许被访问
private:该权限下的成员,只能在类内被访问,子类、类外不允许被访问
2> 如果没有指定权限,则默认为私有权限
3> 一个类中,访问权限可以出现多次,也可以出现在任意地方,一般情况下,我们将同一权限下的成员写到一个关键字下面
4> 一个访问权限的范围,是从当前关键字开始到下一个关键字或者整个类的定义结束为止
5> 一般情况下,成员属性定义成私有的,成员函数定义成公有的
6> 类的访问权限是针对于类体而言的,不是针对于类对象的
#include <iostream>
using namespace std;
//将实现人的所有属性和行为都封装到一个整体,
//后期如果需要一个人,只需要使用该类实例化对象后,通过对象为整个程序服务
class Person
{
string skill = "C++"; //技能
public:
string name = "zhangsan"; //姓名 功能的属性
protected:
int pwd = 123456; //银行卡密码
private:
int money = 1000000; //私房钱
public:
void show()
{
cout<<" name = "<<name<<endl; //公共权限下的成员,类内可以直接访问
cout<<" pwd = "<<pwd<<endl; //受保护权限下的成员,类内可以直接访问
cout<<" money = "<<money<<endl; //私有成员,类内可以直接访问
cout<<" skill = "<<skill<<endl; //默认为私有成员,类内可以直接访问
}
};
/******************************************主程序***************************/
int main()
{
Person p1; //使用类定义变量的过程,称为实例化对象,p1就是Person类的一个对象
//通过p1使用成员
cout<<"name = "<<p1.name<<endl; //公共权限下的成员,类外可以直接访问
//cout<<"pwd = "<<p1.pwd<<endl; //收保护成员,类外无法直接访问
//cout<<"money = "<<p1.money<<endl; //私有成员,类内可以直接访问,类外无法访问
//cout<<"skill = "<<p1.skill<<endl; //默认为私有成员,类内可以直接访问,类外无法访问
p1.show(); //类中公共权限下的成员,类外可以直接访问
return 0;
}
练习:
自定义一个矩形类,包含私有成员属性 高(height)和宽(width),
公共成员函数:初始化宽、高函数
设置宽度函数
设置高度函数
获取宽度函数
获取高度函数
求矩形的周长(Perimeter)函数
求矩形的面积(Area)函数
#include <iostream>
using namespace std;
class rectangle
{
public:
void init(int w, int h); //初始化一个矩形
bool set_width(int w); //设置宽度
bool set_height(int h); //设置高度
int get_width(); //获取宽度
int get_height(); //获取高度
int perimeter(); //获取周长
int area(); //获取面积
private:
int width=0; //矩形宽度
int height=0; //矩形高度
};
bool rectangle::set_width(int w)
{
if(w<=0)
{
cout<<"提供的宽度不合法"<<endl;
return 0;
}
width=w;
return 1;
}
bool rectangle::set_height(int h)
{
if(h<=0)
{
cout<<"提供的高度不合法"<<endl;
return 0;
}
height=h;
return 1;
}
int rectangle::get_width()
{
return width;
}
int rectangle::get_height()
{
return height;
}
int rectangle::perimeter()
{
return 2*(height+width);
}
int rectangle::area()
{
return height*width;
}
void rectangle::init(int w, int h)
{
if(w<=0 || h<=0)
{
cout<<"初始化失败"<<endl;
}
width = w;
height = h;
}
int main()
{
//使用矩形类实例化一个矩形
rectangle r1; //实例化一个对象
r1.init(3,5); //初始化一个矩形
cout<<r1.perimeter()<<endl;
cout<<r1.area()<<endl;
return 0;
}
练习:
在上面练习的基础上,写一个函数,可以比较两个矩形框是否相等
相等条件:宽==宽 高==高
#include <iostream>
using namespace std;
class rectangle
{
public:
void init(int w, int h); //初始化一个矩形
bool set_width(int w); //设置宽度
bool set_height(int h); //设置高度
int get_width(); //获取宽度
int get_height(); //获取高度
int perimeter(); //获取周长
int area(); //获取面积
bool judge_rect(rectangle &r); //定义判断两个矩形框是否相对
private:
int width=0; //矩形宽度
int height=0; //矩形高度
};
bool rectangle::set_width(int w)
{
if(w<=0)
{
cout<<"提供的宽度不合法"<<endl;
return 0;
}
width=w;
return 1;
}
bool rectangle::set_height(int h)
{
if(h<=0)
{
cout<<"提供的高度不合法"<<endl;
return 0;
}
height=h;
return 1;
}
int rectangle::get_width()
{
return width;
}
int rectangle::get_height()
{
return height;
}
int rectangle::perimeter()
{
return 2*(height+width);
}
int rectangle::area()
{
return height*width;
}
//判断两个矩形框是否相等,成员函数版
bool rectangle::judge_rect(rectangle &r)
{
if(r.width==width && r.height==height)
{
return true;
}
return false;
}
void rectangle::init(int w, int h)
{
if(w<=0 || h<=0)
{
cout<<"初始化失败"<<endl;
}
width = w;
height = h;
}
//定义全局函数版本
bool judge_rect(rectangle &r1, rectangle &r2)
{
//判断两个矩形是否相等
if(r1.get_width()==r2.get_width() && r1.get_height()==r2.get_height())
{
return true;
}
return false; //不相等
}
int main()
{
//使用矩形类实例化一个矩形
rectangle r1; //实例化一个对象
r1.init(3,5); //初始化一个矩形
cout<<r1.perimeter()<<endl;
cout<<r1.area()<<endl;
//定义第二个矩形
rectangle r2;
r2.init(3,6);
//if(judge_rect(r1, r2))
if(r1.judge_rect(r2)) //r2.judge_rect(r1)
{
cout<<"两个矩形框相等"<<endl;
}else
{
cout<<"两个矩形框不相等"<<endl;
}
return 0;
}
1.4 分文件编译
1> 头文件
#ifndef RECTANGLE_H
#define RECTANGLE_H
#include <iostream>
using namespace std;
//头文件中主要放:类的声明、全局函数的声明、全局变量的定义、类型重命名、宏定义
class rectangle
{
public:
void init(int w, int h); //初始化一个矩形
bool set_width(int w); //设置宽度
bool set_height(int h); //设置高度
int get_width(); //获取宽度
int get_height(); //获取高度
int perimeter(); //获取周长
int area(); //获取面积
bool judge_rect(rectangle &r); //定义判断两个矩形框是否相对
private:
int width=0; //矩形宽度
int height=0; //矩形高度
};
bool judge_rect(rectangle &r1, rectangle &r2); //全局函数的声明
#endif // RECTANGLE_H
2>源文件
#include"rectangle.h"
bool rectangle::set_width(int w)
{
if(w<=0)
{
cout<<"提供的宽度不合法"<<endl;
return 0;
}
width=w;
return 1;
}
bool rectangle::set_height(int h)
{
if(h<=0)
{
cout<<"提供的高度不合法"<<endl;
return 0;
}
height=h;
return 1;
}
int rectangle::get_width()
{
return width;
}
int rectangle::get_height()
{
return height;
}
int rectangle::perimeter()
{
return 2*(height+width);
}
int rectangle::area()
{
return height*width;
}
//判断两个矩形框是否相等,成员函数版
bool rectangle::judge_rect(rectangle &r)
{
if(r.width==width && r.height==height)
{
return true;
}
return false;
}
void rectangle::init(int w, int h)
{
if(w<=0 || h<=0)
{
cout<<"初始化失败"<<endl;
}
width = w;
height = h;
}
//定义全局函数版本
bool judge_rect(rectangle &r1, rectangle &r2)
{
//判断两个矩形是否相等
if(r1.get_width()==r2.get_width() && r1.get_height()==r2.get_height())
{
return true;
}
return false; //不相等
}
3> 主程序
#include"rectangle.h"
int main()
{
//使用矩形类实例化一个矩形
rectangle r1; //实例化一个对象
r1.init(3,5); //初始化一个矩形
cout<<r1.perimeter()<<endl;
cout<<r1.area()<<endl;
//定义第二个矩形
rectangle r2;
r2.init(3,6);
//if(judge_rect(r1, r2))
if(r1.judge_rect(r2)) //r2.judge_rect(r1)
{
cout<<"两个矩形框相等"<<endl;
}else
{
cout<<"两个矩形框不相等"<<endl;
}
return 0;
}
1.5 this指针
1> this的内含:是类的非静态成员函数所拥有的一个隐藏的形参指针,指代该对象本身起始地址,哪个对象使用我,我就表示哪个对象
2> this指针的格式:类名 * const this;
3> 对于this指针而言,如果没有显性使用该指针,仅仅只是使用成员,那也是默认使用了this指针
3> this指针的使用场景
场景1:当成员函数的形参名和成员变量同名时,可以使用this指针进行区分
场景2:在拷贝复制函数中,需要返回自身引用时,也必须使用this指针(后面讲)
#include <iostream>
using namespace std;
class rectangle
{
public:
void init(int width, int height); //初始化一个矩形
void show()
{
cout<<"width = "<<width<<" height = "<<height<<endl;
}
private:
int width=0; //矩形宽度
int height=0; //矩形高度
};
void rectangle::init(int width, int height)
{
if(width<=0 || height<=0)
{
cout<<"初始化失败"<<endl;
}
this->width = width; //可以通过this指针改变this所指向内存中的值
this->height = height;
cout<<"this = "<<this<<endl;
//this = NULL; //不能更改指针的指向
}
int main()
{
//使用矩形类实例化一个矩形
rectangle r1; //实例化一个对象
r1.init(3,5); //初始化一个矩形
cout<<"&r1 = "<<&r1<<endl; //r1的地址
r1.show();
//在实例化一个对象
rectangle r2;
r2.init(2,2);
cout<<"&r2 = "<<&r2<<endl; //r2的地址
return 0;
}
1.6 类的大小
1> 一个空的类的大小为1字节,用于占位使用,如果后期有成员变量的加入,则会将这一字节的空间分配出去
2> 类中的成员函数不占类的空间大小,但是成员属性会占用类的大小
3> 成员属性分配空间的大小遵循字节对齐原则
4> 如果类中有虚函数(后期讲),那么类中就会多一个虚指针的大小
5> 如果该类是虚继承(后期讲)自父类,那么该类中也会增加一个虚指针的大小
#include <iostream>
using namespace std;
class Temp
{
public:
int num; //整型成员变量
void show()
{
cout<<"num = "<<num<<endl;
}
//类中有虚函数时,就会多分配一个虚指针的空间
virtual void display()
{
cout<<"我是虚函数"<<endl;
}
virtual void display(int )
{
cout<<"我是虚函数"<<endl;
}
};
int main()
{
cout<<"sizeof(Temp) = "<<sizeof(Temp)<<endl;
return 0;
}
二、类中特殊的成员函数(非常重要)
2.1 类中提供的特殊成员函数
1> 特殊的原因:如果用户不显性定义这些函数,系统也会自动提供这些函数,如果用户显性定义了这些函数,那么系统就不提供了。
无论这些特殊的成员函数,是系统提供的还是用户字节定义的,都无需用户手动调用,特殊的时机,系统会自动调用
2> 构造函数、析构函数、拷贝构造函数、拷贝复制函数、移动构造函数、移动复值函数、取地址运算符重载
2.2 构造函数
1> 功能:
使用类去实例化对象时,为对象进行资源的申请以及初始化使用的
2> 定义格式
1、构造函数没有返回值
2、函数名与类同名
3、访问权限,一般为public
4、 类名(形参列表){函数体内容}
3> 调用时机:
当使用类实例化对象时,系统自动调用,无需手动调用
1、栈区空间:使用类实例化对象时,系统自动调用
类名 变量名(实参); //此时就调用的构造函数
2、堆区空间:只有在使用new申请空间时,自动调用构造函数
类名 *指针名; //此时仅仅只是定义一个指针变量,并没有为对象分配空间
指针名 = new 类名(实参); //此时调用的构造函数
#include <iostream>
using namespace std;
class Stu
{
public:
Stu()
{
cout<<"Stu的构造函数"<<endl;
}
};
int main()
{
//在栈区申请对象空间
Stu s1;
//堆区空间申请对象
Stu *ptr; //此时不调用构造函数
ptr = new Stu; //此时调用构造函数
return 0;
}
4> 构造函数分为有参构造和无参构造函数,有参构造函数可以为成员属性进行初始化,这些构造函数之间构成重载关系
5> 一个类中可以有多个构造函数,每个对象仅仅只会调用一个构造函数
6> 如果类中没有定义构造函数,那么,系统会自动提供一个无参构造函数,用于对对象空间的申请,如果程序员自己定义了构造函数,系统就不再提供那个无参构造了,如果还想使用无参构造,需要自己定义一个无参构造
#include <iostream>
using namespace std;
class Stu
{
private:
string name;
int age;
double score;
public:
//无参构造
Stu()
{
cout<<"Stu的无参构造函数"<<endl;
}
//自定义有参构造
Stu(string n, int a)
{
this->name = n;
this->age = a;
cout<<"Stu::有参构造"<<endl;
}
void show()
{
cout<<"name = "<<name<<endl;
cout<<"age = "<<age<<endl;
cout<<"score = "<<score<<endl;
}
};
int main()
{
//在栈区申请对象空间
Stu s1; //此时调用了无参构造,只为对象申请了空间,但是没有为对象初始化
s1.show();
cout<<"**************************************"<<endl;
//申请一个对象,调用有参构造
Stu s2("zhangsan", 18); //此时调用了有参构造
s2.show();
cout<<"**************************************"<<endl;
//堆区空间申请对象
Stu *ptr; //此时不调用构造函数
ptr = new Stu; //此时调用构造函数
return 0;
}
7> 构造函数初始化对象可以使用初始化列表完成
使用方式:类名(形参1,形参2,。。。形参n):成员1(形参1),成员2(形参2),。。。成员n(形参n)
{函数体内容}
说明:在构造函数的小括号后,由冒号引出初始化列表,括号外时成员变量,括号内是形参
#include <iostream>
using namespace std;
class Stu
{
private:
string name;
int age;
double score;
public:
//无参构造
Stu()
{
cout<<"Stu的无参构造函数"<<endl;
}
//自定义有参构造
Stu(string n, int a)
{
this->name = n; //对成员的赋值操作
this->age = a;
cout<<"Stu::有参构造1"<<endl;
}
//定义有参构造:使用初始化列表完成对成员的初始化工作
Stu(string n, int a, double s):name(n),age(a),score(s)
{
cout<<"Stu::有参构造2"<<endl;
}
void show()
{
cout<<"name = "<<name<<endl;
cout<<"age = "<<age<<endl;
cout<<"score = "<<score<<endl;
}
};
int main()
{
//在栈区申请对象空间
Stu s1; //此时调用了无参构造,只为对象申请了空间,但是没有为对象初始化
s1.show();
cout<<"**************************************"<<endl;
//申请一个对象,调用有参构造
Stu s2("zhangsan", 18); //此时调用了有参构造
s2.show();
cout<<"**************************************"<<endl;
//申请一个对象,调用有参构造
Stu s3("lisi", 20, 99.8);
s3.show();
cout<<"**************************************"<<endl;
//堆区空间申请对象
Stu *ptr; //此时不调用构造函数
ptr = new Stu; //此时调用构造函数
return 0;
}
8> 必须使用初始化列表的情况
1、当构造函数的形参名和成员变量名同名时,可以使用初始化列表来解决
2、当类中有const修饰的成员变量时,对该变量也必须进行初始化,使用初始化列表解决
3、当类中有引用成员时,对该成员的操作也必须使用初始化列表完成
4、当类中有其他类的成员子对象时,对该成员的操作也必须使用初始化列表完成,如果没有使用初始化列表调用有参构造,则系统会自动调用成员子对象的无参构造
#include <iostream>
using namespace std;
class Toy
{
public:
Toy() {cout<<"Toy::无参构造"<<endl;}
Toy(string n):name(n) {cout<<"Toy::有参构造"<<endl;}
private:
string name;
};
class Stu
{
private:
string name;
int age;
double score;
const int value; //类中有const类型的成员
int &key; //类中有引用成员
Toy t; //类中有其他类的成员子对象
public:
//无参构造
Stu():value(1314), key(*(new int(520))), t("hello kity")
{
cout<<"Stu的无参构造函数"<<endl;
}
//自定义有参构造
Stu(string n, int a, int &k):value(1314), key(k)
{
this->name = n; //对成员的赋值操作
this->age = a;
cout<<"Stu::有参构造1"<<endl;
}
//定义有参构造:使用初始化列表完成对成员的初始化工作
Stu(string name, int age, double score, int &k):name(name),age(age),score(score),value(1314),key(k)
{
cout<<"Stu::有参构造2"<<endl;
}
void show()
{
cout<<"name = "<<name<<endl;
cout<<"age = "<<age<<endl;
cout<<"score = "<<score<<endl;
}
};
int main()
{
//在栈区申请对象空间
Stu s1; //此时调用了无参构造,只为对象申请了空间,但是没有为对象初始化
s1.show();
cout<<"**************************************"<<endl;
//堆区空间申请对象
Stu *ptr; //此时不调用构造函数
ptr = new Stu; //此时调用构造函数
return 0;
}
2.3 析构函数
1> 功能:在对象消亡时,用于给对象回收空间使用的
2> 定义格式
1、没有返回值
2、函数名:类名前加个波浪线 ~类名
3、权限:一般为public
4、没有参数,所以,一个类中只有一个析构函数,不能进行重载
5、格式: ~类名()
3> 调用时机:当对象的生命周期结束后,用于回收内存空间
栈区:当栈空间释放后,系统会自动调用该类的析构函数
堆区:当使用delete关键字释放对象空间时,系统自动调用
4> 如果没有手动定义析构函数,系统会提供一个析构函数,用于回收类对象的空间,如果手动定义了析构函数,那么,系统就不再提供默认的析构函数了。
#include <iostream>
using namespace std;
class Toy
{
public:
Toy() {cout<<"Toy::无参构造"<<endl;}
Toy(string n):name(n) {cout<<"Toy::有参构造"<<endl;}
private:
string name;
};
class Stu
{
private:
string name;
int age;
double score;
const int value; //类中有const类型的成员
int &key; //类中有引用成员
Toy t; //类中有其他类的成员子对象
int *ptr; //指针成员
public:
//无参构造
Stu():value(1314), key(*(new int(520))), t("hello kity"), ptr(new int(666))
{
cout<<"Stu的无参构造函数"<<endl;
}
//自定义有参构造
Stu(string n, int a, int &k):value(1314), key(k)
{
this->name = n; //对成员的赋值操作
this->age = a;
cout<<"Stu::有参构造1"<<endl;
}
//定义有参构造:使用初始化列表完成对成员的初始化工作
Stu(string name, int age, double score, int &k):name(name),age(age),score(score),value(1314),key(k)
{
cout<<"Stu::有参构造2"<<endl;
}
void show()
{
cout<<"name = "<<name<<endl;
cout<<"age = "<<age<<endl;
cout<<"score = "<<score<<endl;
}
//定义析构函数
~Stu()
{
delete ptr; //释放指针的空间
cout<<"STU::析构函数"<<endl;
}
};
int main()
{
//在栈区申请对象空间
Stu s1; //此时调用了无参构造,只为对象申请了空间,但是没有为对象初始化
s1.show();
cout<<"**************************************"<<endl;
//堆区空间申请对象
Stu *ptr; //此时不调用构造函数
ptr = new Stu; //此时调用构造函数
//释放ptr的空间
delete ptr; //此时会调用析构函数
return 0;
}
作业
仿照string类,实现myString
#include <iostream>
#include <cstring>
class myString
{
private:
char *str; //记录c风格的字符串
int size; //记录字符串的实际长度
public:
//无参构造
myString():size(10)
{
str = new char[size]; //构造出一个长度为10的字符串
str[0] = '\0'; // 初始化为空字符串
}
//有参构造
myString(const char *s)
{
size = strlen(s); // 计算传入字符串的长度
str = new char[size + 1]; // 分配足够的内存空间,加1是为了存储结束符'\0'
strcpy(str, s); // 复制传入的字符串到新分配的内存空间
}
//判空函数
bool empty() const
{
return str[0] == '\0';
}
//size函数
int length() const
{
return size;
}
//c_str函数
const char* c_str() const
{
return str;
}
//at函数
char at(int index) const
{
if (index >= 0 && index < size)
{
return str[index];
}
else
{
throw std::out_of_range("Index out of range");
}
}
//二倍扩容
void doubleCapacity()
{
int newSize = size * 2;
char *newStr = new char[newSize + 1]; // 分配新的内存空间,加1是为了存储结束符'\0'
strcpy(newStr, str); // 复制原字符串到新内存空间
delete[] str; // 释放原内存空间
str = newStr; // 更新指针指向新的内存空间
size = newSize; // 更新字符串长度
}
};
int main()
{
myString s1;
myString s2("Hello, world!");
std::cout << "s1是" << (s1.empty() ? "空" : "非空") << std::endl;
std::cout << "s2是" << (s2.empty() ? "空" : "非空") << std::endl;
std::cout << "s1的长度:" << s1.length() << std::endl;
std::cout << "s2的长度:" << s2.length() << std::endl;
std::cout << "s2的内容:" << s2.c_str() << std::endl;
std::cout << s2.at(7) << std::endl;
return 0;
}