一、类的引出概述
在c语言结构体中,行为和属性是分开的,万一调用错误,将会导致问题发生。c++中类将数据和方法封装在一起,加以权限区分,用户只能通过公共方法 访问 私有数据。
二、封装
封装特性包含两个方面,一个是属性和变量合成一个整体,一个是给属性和函数增加访问权限。
1.把变量(属性)和函数(操作)合成一个整体,封装在一个类中
2.对变量和函数进行访问控制访问权限
3.在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问4.在类的外部(作用域范围外),访问权限才有意义:.public、private、protected
5.在类的外部,只有public.修饰的成员太能被访问,在没有涉及继承与派生时,private和protected是相同等级的.外部不允许访问
访问属性 | 属性 | 对象内部 | 对象外部 |
public | 公有 | 可访问 | 可访问 |
private | 保护 | 可访问 | 不可访问 |
protected | 私有 | 可访问 | 不可访问 |
尽量设置成员变量为私有权限,将方法设置成公有。
优点:
对变量的设置时的控制
实现变量设置只读权限
实现变量设置只写权限
实现变量设置可读可写权限
struct和class的区别:class默认访问权限为.private,struct默认访问权限为public.
三、类的案例(了解)
1、设计一个person类
设计一个Person类,Person类具有name和age属性,提供初始化函数(Init),并提供对name和age 的读写函数(set,get),但必须确保age的赋值在有效范围内(0-100),超出有效范围,则拒绝赋值,并 提供方法输出姓名和年龄。----需要六个函数
#include <iostream>
using namespace std;
#include<string.h>
class Person
{
private:
char mName[32];
int mAge;
public:
//初始化成员
void init(char *name, int age)
{
strcpy(mName, name);
if(age>=0 && age<=100)
{
mAge = age;
}
else
{
cout<<"年龄无效"<<endl;
}
return;
}
//设置name
void setName(char *name)
{
strcpy(mName, name);
}
//获取name
char *getName(void)
{
return mName;
}
//设置age
void setAge(int age)
{
if(age>=0 && age<=100)
{
mAge = age;
}
else
{
cout<<"年龄无效"<<endl;
}
}
//得到age
int getAge(void)
{
return mAge;
}
//显示所有数据
void showPerson(void)
{
cout<<mName<<" "<<mAge<<endl;
}
};
void test02()
{
Person ob1;
ob1.init("lucy", 18);
ob1.showPerson();
ob1.setName("bob");
cout<<"年龄:"<<ob1.getAge()<<endl;
ob1.showPerson();
}
2、设计一个立方体类
设计立方体类(Cube),求出立方体的面积( 2ab +2ac +2bc )和体积( a*b*c),分别用全局函数和成员函数判断两个立方体是否相等。
class Cube
{
private:
int mA;
int mB;
int mC;
public:
void setA(int a)
{
mA = a;
}
int getA(void)
{
return mA;
}
void setB(int b)
{
mB = b;
}
int getB(void)
{
return mB;
}
void setC(int c)
{
mC = c;
}
int getC(void)
{
return mC;
}
//获取面积
int getS(void)
{
return (mA*mB+mB*mC+mC*mA)*2;
}
//获取体积
int getV(void)
{
return mA*mB*mC;
}
//成员函数 比较两个立方体是否先等
bool compareCube01(Cube &ob)
{
if(mA==ob.mA && mB ==ob.mB && mC == ob.mC)
{
return true;
}
return false;
}
};
//全局函数 比较两个立方体是否先等
bool compareCube02(Cube &ob1, Cube &ob2)
{
if(ob1.getA()==ob2.getA() && ob1.getB() ==ob2.getB() && ob1.getC() == ob2.getC())
{
return true;
}
return false;
}
void test()
{
Cube ob1;
ob1.setA(10);
ob1.setB(20);
ob1.setC(30);
cout<<"面积:"<<ob1.getS()<<endl;
cout<<"体积:"<<ob1.getV()<<endl;
Cube ob2;
ob2.setA(10);
ob2.setB(20);
ob2.setC(30);
// if(compareCube02(ob1, ob2))
if(ob1.compareCube01(ob2))
{
cout<<"相等"<<endl;
}
else
{
cout<<"不相等"<<endl;
}
}
int main(int argc, char *argv[])
{
test();
return 0;
}
3、点和圆的关系
设计一个圆形类(AdvCircle),和一个点类(Point),计算点和圆的关系。
class Point
{
private:
int mX;
int mY;
public:
void setX(int x)
{
mX = x;
}
int getX(void)
{
return mX;
}
void setY(int y)
{
mY = y;
}
int getY(void)
{
return mY;
}
};
class Circle
{
private:
Point p;//对象作为类的成员变量
int mR;
public:
void setPoint(int x, int y)
{
p.setX(x);
p.setY(y);
}
Point getPoint(void)//打印圆点
{
return p;
}
void setR(int r)
{
mR = r;
}
int getR(void)
{
return mR;
}
//判断点 在圆的位置
int pointIsOnCircle(Point &ob)
{
int len = (ob.getX()-p.getX())*(ob.getX()-p.getX())+\
(ob.getY()-p.getY())*(ob.getY()-p.getY());
if(len == mR*mR)
{
return 0;
}
else if(len > mR*mR)
{
return 1;
}
else if(len < mR*mR)
{
return -1;
}
}
};
void test()
{
//实例化一个点的对象
Point p;
p.setX(5);
p.setY(5);
//实例化一个圆的对象
Circle cir;
cir.setPoint(2,2);
cir.setR(5);
if(cir.pointIsOnCircle(p) == 0)
{
cout<<"点在圆上"<<endl;
}
else if(cir.pointIsOnCircle(p) > 0)
{
cout<<"点在圆外"<<endl;
}
else if(cir.pointIsOnCircle(p) < 0)
{
cout<<"点在圆内"<<endl;
}
}
4、类中成员函数在类外实现
5、类在其他文件实现
头文件定义类, cpp实现类的成员函数
data.h
#ifndef DATA_H
#define DATA_H
class Data
{
private:
int mA;
public:
int getA(void);
void setA(int a);
};
#endif
data.cpp
#include "data.h"
int Data::getA()
{
return mA;
}
void Data::setA(int a)
{
mA = a;
}
main.cpp
#include <iostream>
#include "data.h"
using namespace std;
int main(int argc, char *argv[])
{
Data ob;
ob.setA(100);
cout<<ob.getA()<<endl;
return 0;
}
对象的构造和析构:
当我们创建对象的时候,这个对象应该有一个初始状态,当对象销毁之前应该销毁自己创建的一些数据。对象的初始化和清理也是两个非常重要的安全问题,C++为了给我们提供这种问题的解决方案,构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化(创建对象时为对象成员属性赋值)和对象清理工作。初始化和清理工作是编译器强制我们要做的事情,即使你不提供初始化操作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事。
四、构造函数(初始化工作)
1、概述
构造函数是类实例化对象时自动调用
2、创建
构造函数名与类名称相同,不能有返回值类型(连void都不可以),可以有参数(支持重载),必须加public权限。
3、分类
无参构造、有参构造。
4、调用
类实例化对象时:先为对象开辟空间 然后才调用构造函数。
1、如果用户不提供构造函数 编译器会自动 提供一个无参空的构造函数。
2、如果用户提供构造函数 编译器会自动 屏蔽默认无参的构造
四种:隐式调用、显示调用、隐式转换、匿名调用
注:写任何一个类 无参构造, 有参构造都需要实现
5、explicit关键字防止构造函数隐式转换
explicit修饰构造函数,防止构造函数隐式转换,避免令人产生赋值误会。
允许隐式转换:
//此时允许有参构造隐式转换
A(int a)
{
mA = a;
cout<<"A的有参构造mA="<<mA<<endl;
}
//构造函数隐式转换(类中只有一个数据成员)
A ob1=100;//ok
不允许隐式转换:
//防止有参构造 隐式转换
explicit A(int a)
{
mA = a;
cout<<"A的有参构造mA="<<mA<<endl;
}
//构造函数隐式转换(类中只有一个数据成员)
A ob1=100;//err 转换失败
五、析构函数(清理工作)
当对象生命周期结束的时候 系统自动调用析构函数。
函数名和类名称相同,在函数名前加~,没有返回值类型,没有函数形参。(不能被重载) 先调用析构函数 再释放对象的空间。
调用释放顺序:括号内的最先释放,释放先进后出
一般情况下,空的析构函数就足够。但是如果一个类有指针成员,这个类必须 写析构函数,释放指针成员所指向空间。
#include<iostream>
#include<stdlib.h>
#include<string.h>
using namespace std;
class Data
{
public:
char *name;
public:
Data()
{
name = NULL;
}
Data(char *str)
{
name = (char*)calloc(1, strlen(str) + 1);
strcpy(name, str);
cout << "有参构造 name=" << name << endl;
}
~Data()
{
cout << "析构函数" <<name<< endl;
if (name != NULL)
{
free(name);
name = NULL;
}
}
};
void test()
{
Data ob("hello world");
}
六、拷贝构造函数
1、定义
拷贝构造函数本质是构造函数
拷贝构造的调用时机:旧对象 初始化 新对象 时。
如果用户不提供拷贝构造 编译器会自动提供一个默认的拷贝构造(完成赋值动作--浅拷贝)
2、拷贝构造 和 无参构造 有参构造的关系
如果用户定义了 拷贝构造或者有参构造 都会屏蔽无参构造。
如果用户定义了 无参构造或者有参构造 不会屏蔽拷贝构造。
3、拷贝构造调用形式
(1)旧对象给新对象初始化 调用拷贝构造
Data ob1(10);
Data ob2 = ob1;//调用拷贝构造
(2)普通对象作为函数参数 调用函数时 会发生拷贝构造
(3)函数返回值普通对象 (Visual Studio会发生拷贝构造)
Data get(void)
{
Data ob1(10);
return ob1;
}
void test()
{
Data ob2 = get();
}
注:给对象取别名 不会调用拷贝构造
Data ob1(10);
Data &ob2 = ob1;//不会调用拷贝构造
4、拷贝构造中浅拷贝和深拷贝
如果类中没有指针成员, 不用实现拷贝构造和析构函数。
如果类中有指针成员和拷贝构造调用, 必须实现析构函数释放指针成员指向的堆区空间,必须实现拷贝构造完成深拷贝动作(因拷贝构造释放时相应堆区空间需再释放一次,故堆区空间也需重新拷贝一次)
七、初始化列表
成员对象:一个类的对象 作为另一个类的成员
类会自动调用成员对象的无参构造,如果类中想调用成员对象的有参构造 必须使用初始化列表。
class A
{
public:
int mA;
public:
A()
{
mA = 0;
cout << "A的无参构造" << endl;
}
A(int a)
{
mA = a;
cout << "A的有参构造" << endl;
}
~A()
{
cout << "A的析构函数" << endl;
}
};
class B
{
public:
int mB;
A ob;//成员对象
public:
B()
{
cout << "B类的无参构造" << endl;
}
//初始化列表 成员对象 必须使用对象名+() 重要
B(int a, int b) :ob(a)
{
mB = b;
cout << "B类的有参构造" << endl;
}
~B()
{
cout << "B的析构函数" << endl;
}
};
int main(int argc, char* argv[])
{
B ob1(10, 20);
cout << "mA =" << ob1.ob.mA << ", mB =" << ob1.mB << endl;
return 0;
}
八、对象数组
对象数组:本质是数组 数组的每个元素是对象