类和对象
一、封装
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
示例:
#include <iostream>
using namespace std;
class Student{
public:
void setName(string name){
m_Name=name;
}
void setID(int id){
m_id=id;
}
void showStudent(){
cout<<"name:"<<m_Name <<"ID:"<<m_id<<endl;
}
public:
string m_Name;
int m_id;
};
int main(){
Student stu;
stu.setName("德玛西亚");
stu.setID(250);
stu.showStudent();
system("pause");
return 0;
}
意义二:
- 类在设计时,可以把属性和行为放在不同的权限下,加以控制
- 访问权限有三种:
- piblic 公共权限 成员 类内可以访问,类外可以访问
- protected 保护权限 成员 类内可以访问,类外可以访问,儿子可以访问父亲的保护内容
- private 私有权限 成员 类内可以访问,类外可以访问 ,儿子不可以访问父亲的私有内容
struct和class区别:
在C++中,struct和class唯一的区别在于默认的访问权限不同。
- struct访问权限为公共
- class访问权限为私有
成员属性设置为私有
- 优点1:将所有成员属性设置为私有,可以自己控制读写权限
- 优点2:对于写权限,我们可以检测数据的有效性
示例:
#include <iostream>
using namespace std;
class Person{
private:
string m_Name;
int m_Age;
string m_Lover;
public:
void setName(string name){
m_Name=name;
}
string getName(){
return m_Name;
}
int getAge(){//只读
m_Age=0;//初始化为0岁
return m_Age;
}
void setLover(string lover){//只写
m_Lover=lover;
}
};
int main(){
Person p;
p.setName("张三");
cout<<"姓名为:"<<p.getName()<<endl;
cout<<"年龄为:"<<p.getAge()<<endl;
system("pause");
return 0;
}
封装案例:
#include <iostream>
using namespace std;
//点类:
class Point{
public:
//设置X
void setX(int x){
m_X=x;
}
//获取X
int getX(){
return m_X;
}
//设置y
void setY(int y){
m_Y=y;
}
//获取y
int getY(){
return m_Y;
}
private:
int m_X;
int m_Y;
};
//圆类
class Circle{
public:
//设置半径
void setR(int r){
m_R=r;
}
int getR(){
return m_R;
}
//设置圆心
void setCenter(Point center){
m_Center=center;
}
Point getCenter(){
return m_Center;
}
private:
int m_R;
Point m_Center;//在类中可以让另一个类作为本类成员
};
//判断点和圆关系
void isInCircle(Circle &c,Point &p){
//计算两点之间距离平方
int distance=(c.getCenter().getX()-p.getX())*(c.getCenter().getX()-p.getX())+(c.getCenter().getY()-p.getY())*(c.getCenter().getY()-p.getY());
//计算半径平方
int rdistance=c.getR()*c.getR();
//判断关系
if(distance==rdistance){
cout<<"点在圆上"<<endl;
}else if(distance>rdistance){
cout<<"点在圆外"<<endl;
}else{
cout<<"点在圆内"<<endl;
}
}
int main(){
Circle c;
c.setR(10);
Point p;
p.setX(10);
p.setY(10);
isInCircle(c,p);
return 0;
}
二、对象的初始化和清理
构造函数和析构函数
- 构造函数:主要做用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
- 析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作。
构造函数方法::void(){}
- 没有返回值也不写void;
- 函数名称和类名相同;
- 构造函数有参数,因此可以发生重载
- 程序在调用对象时会自动调用构造函数,无需手动调用,而且只会调用一次
析构函数方法:~类名(){}
- 没有返回值也不写void;
- 函数名称和类名相同, 在名称前加上符号~;
- 构造函数没有参数,因此不可以发生重载;
- 程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
示例:
#include <iostream>
using namespace std;
class Person{
public:
Person(){
cout<<"Person的构造函数调用"<<endl;
}
~Person(){
cout<<"Person的析构函数调用"<<endl;
}
};
void test(){
Person p;//默认调用构造函数
}
int main(){
test();
system("pause");
return 0;
}
构造函数的分类及调用
- 两种分类方式:
按参数分类:有参构造和无参构造
按类型分类:普通构造和拷贝构造 - 三中调用方式:
括号法、显示法和隐式转换法
示例:
#include <iostream>
using namespace std;
class Person{
public:
//无参(默认)构造函数
Person(){
cout<<"无参构造函数调用"<<endl;
}
//有参构造函数
Person(int a){
age=a;
cout<<"有参构造函数调用"<<endl;
}
//拷贝构造函数
Person(const Person &p){
age=p.age;
cout<<"拷贝构造函数调用"<<endl;
}
~Person(){
cout<<"Person的析构函数调用"<<endl;
}
public:
int age;
};
void test1(){
Person p;//默认调用构造函数
}
void test2(){
Person p1(10);//括号法
Person p2=Person(10);//显示法
Person p3=Person(p2);//显示法
Person p4=10;//隐式转换法
Person p5=p4; //隐式转换法
}
int main(){
test1();
test2();
system("pause");
return 0;
}
拷贝构造函数调用时机
- C++中拷贝构造函数调用通常有三种情况:
- 使用一个已经创建完毕的对象初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
构造函数调用规则
- 默认情况下,C++编译器至少给一个类添加了3个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
深拷贝与浅拷贝
- 浅拷贝:简单的复制拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
示例:
#include <iostream>
using namespace std;
class Person{
public:
//无参(默认)构造函数
Person(){
cout<<"无参构造函数调用"<<endl;
}
//有参构造函数
Person(int age,int height){
m_Age=age;
m_Height=new int(height);
cout<<"有参构造函数调用"<<endl;
}
~Person(){
if(m_Height!=NULL){
delete m_Height;
m_Height=NULL;
}
cout<<"Person的析构函数调用"<<endl;
}
//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person &p){
cout<<"Person拷贝构造函数调用"<<endl;
m_Age=p.m_Age;
//m_Height=p.m_Height;
//编译器默认实现就是这行代码
//深拷贝操作
m_Height=new int(*p.m_Height);
}
public:
int m_Age;
int *m_Height;
};
void test1(){
Person p1(18,160);
cout<<"p1的年龄为:"<<p1.m_Age<<"身高为:"<<*p1.m_Height<<endl;
Person p2(p1);
cout<<"p2的年龄为:"<<p2.m_Age<<"身高为:"<<*p2.m_Height<<endl;
}
int main(){
test1();
system("pause");
return 0;
}
初始化列表:
-
作用:
C++提供了初始化列表语法,实现初始化属性 -
语法:
构造函数():属性1(值1),属性2(值2),······{}
类对象作为类成员
- C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
- 例如:
class A{};
class B
{
A a;
};
B中有对象A作为成员,A为对象成员
示例:
#include <iostream>
using namespace std;
class Phone
{
public:
Phone(string name){
m_PhoneName=name;
cout<<"Phone构造"<<endl;
}
~Phone(){
cout<<"Phone析构"<<endl;
}
string m_PhoneName;
};
class Person
{
public:
//初始化列表可以告诉编译器哪一个构造函数
Person(string name,string pName):m_Name(name),m_Phone(pName){
cout<<"Person构造"<<endl;
}
~Person(){
cout<<"Person析构"<<endl;
}
void playGame(){
cout<<m_Name<<"使用"<<m_Phone.m_PhoneName<<endl;
}
string m_Name;
Phone m_Phone;
};
void test1(){
Person p("张三","苹果X");
p.playGame();
}
int main(){
test1();
system("pause");
return 0;
}
注意:
- 构造的顺序是:先调用对象成员的构造,再调用本类构造,析构顺序与构造相反
静态成员:
- 静态成员就是在成员变量和成员函数前加上关键字static,成为静态成员
- 静态成员分为:
- 静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化 - 静态成员函数
所有对象共享同一函数
静态成员函数只能访问静态成员变量
三、C++对象模型和this指针
成员变量和成员函数分开存储
- 在C++中,类内的成员变量和成员函数分开存储
- 只有非静态成员变量才属于类的对象上
this指针概念
- this指针只想被调用的成员函数所属的对象
- this指针是隐含每一个非静态成员函数内的一种指针
- this指针不需要定义,直接使用即可
- this指针用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this;
四、友元
- 在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
- 友元的目的就是让一个函数或者类访问另一个类中私有成员
- 友元的关键字为friend
- 友元的三种实现:
- 全局函数做友元
- 成员函数做友元
- 类做友元
示例:
#include <iostream>
using namespace std;
class Building;
class goodGay
{
public:
goodGay();
void visit();//只让visit寒素作为Building的好朋友,可以访问Building中私有内容
void visit2();
private:
Building *building;
};
class Building
{
//告诉编译器,goodGay类中的visit成员函数时Building好朋友,可以访问私有内容
friend void goodGay::visit();
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building(){
this->m_SittingRoom="客厅";
this->m_BedRoom="卧室";
}
goodGay::goodGay(){
building=new Building;
}
void goodGay::visit(){
cout<<"好基友正在访问"<<building->m_BedRoom<<endl;
cout<<"好基友正在访问"<<building->m_SittingRoom<<endl;
}
void test1(){
goodGay gg;
gg.visit();
}
void test2(){
goodGay gg;
gg.visit2();
}
int main(){
test1();
test2();
system("pause");
return 0;
}
五、运算符重载
- 运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加法运算符重载:
作用:实现两个自定义数据类型相加的运算
#include <iostream>
using namespace std;
class Person
{
public:
Person(){};
Person(int a,int b){
this->m_A=a;
this->m_B=b;
}
//成员函数实现+号运算符重载
Person operator+(const Person &p){
Person temp;
temp.m_A=this->m_A+p.m_A;
temp.m_B=this->m_B+p.m_B;
return temp;
}
public:
int m_A;
int m_B;
};
//成员函数实现+号运算符重载
Person operator+(const Person &p1,const Person &p2){
Person temp(0,0);
temp.m_A=p1.m_A+p2.m_A;
temp.m_B=p2.m_B+p2.m_B;
return temp;
}
//运算符重载,可以发生函数重载
Person operator+(const Person &p2,int val){
Person temp;
temp.m_A=p2.m_A+val;
temp.m_B=p2.m_B+val;
return temp;
}
void test(){
Person p1(10,10);
Person p2(20,20);
//成员函数方式
Person p3=p2+p1;
cout<<"m_A="<<p3.m_A<<"m_B="<<p3.m_B<<endl;
Person p4=p3+10;
cout<<"m_A="<<p4.m_A<<"m_B="<<p4.m_B<<endl;
}
int main(){
test();
system("pause");
return 0;
}
六、继承
- 继承是面向对象三大特性之一
例如我们看到很多网站中都i有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同,接下来我们利用继承的写法来实现。 - 示例:
#include <iostream>
using namespace std;
//公共页面
class BasePage
{
public:
void header(){
cout<<"首页、公开课、登录、注册······(公共头部)"<<endl;
}
void footer(){
cout<<"帮助中心、交流合作、站内地图······(公共底部)"<<endl;
}
void left(){
cout<<"Java,Python,C++······(公共分类列表)"<<endl;
}
} ;
//Java
class Java:public BasePage
{
public:
void content(){
cout<<"JAVA学科视频"<<endl;
}
};
//Python
class Python:public BasePage
{
public:
void content(){
cout<<"Python学科视频"<<endl;
}
};
//C++
class C:public BasePage
{
public:
void content(){
cout<<"C++学科视频"<<endl;
}
};
void test(){
//Java页面
cout<<"Java下载视频页面"<<endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout<<"················"<<endl;
//Python页面
cout<<"Python下载视频页面"<<endl;
Python p;
p.header();
p.footer();
p.left();
p.content();
cout<<"················"<<endl;
//C++页面
cout<<"C++下载视频页面"<<endl;
C c;
c.header();
c.footer();
c.left();
c.content();
cout<<"················"<<endl;
}
int main(){
test();
system("pause");
return 0;
}
- 总结:
继承的好处:可以减少重复的代码
class A : classB
A类称为子类或者派生类
B类称为父类或者基类
题目
算法步骤:
- 创建学生类:
属性:学号、姓名、身高、体重
成员函数:
void setdata(int r = 0, string n = 0, int h = 0, int w = 0) {}给学生属性赋值,存储学生相关信息;
void changedata(Student& s) {}将形参的值与成员变量的值互换 - 在主函数中,输入n,利用vector容器设一个数组,用来存储n个学生类。 输入n个学生的属性的值并调用成员函数setData()将这n个学生的属性的值存入;
- 利用冒泡排序将n个宿舍号进行从大到小排序,调用成员函数changeData()交换学生信息;
- 总体排过序后,将宿舍号相同的学生身高进行比较,在每一宿舍的学生比较完之后输出这个宿舍身高最高的同学的学生信息
其中这一部分可以通过容器map实现,宿舍号作为key值,身高作为value值。
代码:
#include<iostream>
#include<iomanip>
#include<vector>
using namespace std;
class Student {
public:
string name;
int height, weight, room;
void setdata(int r = 0, string n = 0, int h = 0, int w = 0) {
room = r;
name = n;
height = h;
weight = w;
}
void changedata(Student& s) {
string c_name;
int c_height, c_weight, c_room;
c_name = this->name;
c_height = this->height;
c_weight = this->weight;
c_room = this->room;
this->name = s.name;
this->height = s.height;
this->weight = s.weight;
this->room = s.room;
s.name = c_name;
s.height = c_height;
s.weight = c_weight;
s.room = c_room;
}
};
int main() {
int n;
cin >> n;
vector<Student> s(10000);
string name;
int height, weight, room;
for (int i = 0; i < n; i++) {//输入并存入每个学生的数据
cin >> room >> name >> height >> weight;
s[i].setdata(room, name, height, weight);
}
//先将全部的宿舍号进行排序
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
if (s[i].room > s[j].room) { s[i].changedata(s[j]); }
}
}
//总体排过序后,将宿舍号相同的学生身高进行比较
for (int i = 0, j = 0, max = 0; i < n;) {
max = j;
for (; s[i].room == s[j].room; j++) {//冒泡排序
if (s[j].height >= s[max].height)
{
max = j;
}
}
//输出同一宿舍的身高最高的学生的数据
cout << setfill('0') << setw(6) << s[max].room << " " << s[max].name << " " << s[max].height << " " << s[max].weight << endl;
i = j;//跳转到下一宿舍
}
system("pause");
return 0;
}
总结:
写这一题我用了很长时间,用三个小时写出代码,花费比较长的时间修修改改,最后才写出来,感觉写完这道题,我的C++上升了一档次。最后想说,功夫不负有心人。