目录
1.1封装
1.1.2struct和class的区别
1.1.3成员属性设置为私有
1.2对象初始化和处理
1.2.1构造函数和析构函数
1.2.2构造函数的分类及调用
1.2.3拷贝函数的使用
1.2.4构造函数的调用规则
1.2.5浅拷贝和深拷贝
1.2.6初始化
1.2.8静态成员变量和静态成员函数
1.3对象模型和this指针
1.3.1成员变量和成员函数是分开存储的
1.3.2this指针
1.3.3空指针访问成员函数
1.3.4const 修饰成员函数
1.4友元
1.5运算符重载
代码中避免野指针
类:由数据成员定义和成员函数定义构成的一段独立代码
对象:用类定义的存储单元(定义:1.student zhang wang;2.class student {...}zhang,wang;3.class{...}zhang wang;第三个不建议)
内置函数/内联函数(inline)用空间换时间,用函数体替换调用。inline 只有在于代码体在一起是才有用(一般不提倡)
eg:void Foo(int x, int y);
inline void Foo(int x, int y) // inline 与函数定义体放在一起
面向对象的四大特征:抽象、封装、继承、多态
意义一:设计类的时候,属性和行为写在一起,表现事物
类中的属性和行为被统称为成员
属性---被称为成员属性或者成员变量
行为---被称为成员函数或者成员方法
案例:设计一个圆类,来设计圆的周长。
实例化:通过一个类来创建一个对象的过程
#include
using namespace std;
//周长公式:C=2*PI*r
const double PI = 3.14;
//class代表设计一个类,类后面就是类名
class yuan
{
//访问权限(公共权限)
public:
//属性(通常为变量)--半径
int r;
//行为(通常用函数)--获取圆的周长
double ZC()
{
return 2 * PI*r;
}
};
//以上为类的声明语句
int main()
{
//通过圆类,创建一个具体的圆(对象)
//类的定义语句,定义了一个名叫c1的对象
yuan c1;
yuan *pc1=c1;
//访问对象成员
c1.r//“.”读作谁的谁,该语句等价为pc1->c1,“->”读作;谁的指向对象的成员
//给圆进行赋值
c1.r = 10;
cout << "c1圆的周长为:" <
案例:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号。(通过行为给属性赋值)
#include
using namespace std;
#include
class student
{
//权限:
public:
//属性--姓名--学号
string name;
int id;
//行为--显示学生的姓名和学号
void show()
{
cout << "学号:" << id << "姓名:" << name << endl;
}
//通过行为给属性赋值
void setname(string n)
{
name = n;
}
void setid(int i)
{
id = i;
}
};
int main()
{
//通过类,创建一个具体的学生对象
student s1;
//s1.id = 21;
s1.setid(21);
//s1.name = "张三";
s1.setname("张三");
s1.show();
system("pause");
return 0;
}
在类外定义成员函数:void student::show(){...}(::--->称为预运算符,叫做student的show函数)不建议在类外定义!!!
意义二:在设计类时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
class person
{
public:
string name;
//保护权限--车子
protected:
string car;
//私有权限--银行密码
private:
int password;
//类内访问
public:
void text()
{
name = "张三";
car = "拖拉机";
password = 12345;
}
};
int main()
{
//类外访问
person p1;
p1.name = "李四";
//p1.password;----错,私有类外不可以访问
system("pause");
return 0;
}
1.1.2struct和class的区别
在c++中,二者的唯一区别:
默认的访问权限不同
1.1.3成员属性设置为私有
1、可以自己控制读写权限。
2、对于写,可以检测数据的有效性,防止数据超出有效范围。
案例:立方体案例,利用全局函数和成员函数判断两个立方体是否相等
#include
using namespace std;
class cube{
//行为
public:
//设置长
void set_l(int x)
{
l = x;
}
//获取长
int getl()
{
return l;
}
//设置宽
void set_w(int x)
{
w = x;
}
//获取宽
int getw()
{
return w;
}
//设置高
void set_h(int x)
{
h = x;
}
//获取高
int geth()
{
return h;
}
//获取立方体面积
int gets()
{
return 2 * w*h + 2 * w*l + 2 * l*h;
}
//获取立方体体积
int getv()
{
return h*w*l;
}
bool issame(cube &c)
{
if (h == c.geth()&&l == c.getl()&&w == c.getw())
return true;
else
return false;
}
//属性
private:
int h;//高
int w;//宽
int l;//长
};
bool issame(cube &c1,cube &c2)
{
if (c1.geth() == c2.geth()&&c1.getl() == c2.getl()&&c1.getw()== c2.getw())
return true;
else
return false;
}
int main()
{
cube c1;
c1.set_h(10);
c1.set_l(10);
c1.set_w(10);
cout << "c1立方体的面积为:" << c1.gets() << endl;
cout << "c1立方体的体积为:" << c1.getv()<< endl;
cube c2;
c2.set_h(10);
c2.set_l(10);
c2.set_w(10);
bool n = issame(c1, c2);
if (n)
{
cout << "全局函数:c1和c2相等" << endl;
}
else {
cout << "全局函数:c1和c2不想等" << endl;
}
bool x = c1.issame(c2);
if (n)
{
cout << "成员函数:c1和c2相等" << endl;
}
else {
cout << "成员函数:c1和c2不想等" << endl;
}
system("pause");
return 0;
}
1.2.1构造函数和析构函数
如果我们不给提供构造函数或者析构函数,那么编译器会自己提供,只不过编译器提供的为空实现。
构造函数语法:类名(){}
析构函数语法:~类名(){}
其中p是创建在栈区的一个局部变量,在text运行完,这个变量就会被销毁。
要是代码修改如下,执行结果会是什么样?
int main()
{
//text();
person p;
system("pause");
return 0;
}
运行结果如下:
因为p创建在main函数里,system这行代码会让程序暂停,因此指责个变量还没销毁,因此在随意按任意键之后return之前该变量才会被销毁“析构函数的调用”会一闪而过。
1.2.2构造函数的分类及调用
分类:
应用如下:
#include
using namespace std;
class person{
int age;
public:
person(){
cout << "无参构造函数的调用" << endl;
}
~person()
{
cout << "析构函数的调用" << endl;
}
//有参
person(int a)
{
age = a;
cout << "有参构造函数的调用" << endl;
}
//拷贝
person(const person &p)
{
age = p.age;
cout << "拷贝构造函数的调用" << endl;
}
};
void text()
{
//括号法:
/*
person p1;
person p2(10);//有参
person p3(p2);//拷贝
*/
//显示法:
/*
person p1;
person p2 = person(10);
person p3 = person(p2);
*/
//隐式转换法:
person p2 = 10;
person p3 = p2;
}
int main()
{
text();
system("pause");
return 0;
}
注意1:括号法中调用无参构造函数不能加(),因为会被编译器认为是函数的声明。
注意2:显示法中等号右边的person(10)等单独写就是匿名对象,该行执行结束后系统会立即收回匿名对象。
注意3:不能利用拷贝函数初始化匿名对象 eg:person(p2),系统会自动认为person(p2)===person p2
1.2.3拷贝函数的使用
拷贝构造函数会创建一个新的对象,不是原来的对象了
1.2.4构造函数的调用规则
默认情况下,c++编译器会给一个类提供3个函数:默认构造函数、默认析构函数、默认拷贝函数。
注意1:如果用户提供了有参构造函数,则编译器不会再提供无参函数,但是还是会提供拷贝函数
注意2:如果用户只提供了拷贝函数,则编译器不会再提供无参构造函数和有参构造函数
1.2.5浅拷贝和深拷贝
析构函数就是将堆区开辟的数据释放
#include
using namespace std;
class person{
public:
person(int x,int n)
{
cout << "有参构造函数的调用" << endl;
age = x;
height = new int(n);
}
~person()
{
//析构函数就是将堆区开辟的数据释放
if (height != NULL)
delete height;
//delete只是把指针所指的内存释放掉了,并没有改变指针的值,会出现野指针(指向了一个非法的或者已经被销毁,会对系统不利)。
height = NULL;
cout << "析构函数的调用" << endl;
}
int age;
int *height;
};
void text()
{
person p1(18, 180);
cout << "年龄为:" << p1.age << "身高为:" << *p1.height << endl;
person p2(p1);
cout << "年龄为:" << p2.age << "身高为:" << *p2.height << endl;
}
int main()
{
text();
system("pause");
return 0;
}
该代码会报错,那是为什么?
原因如下:栈区是先进后出,所以右边拷贝函数里面的先释放,然后到了左边函数释放的时候该堆区里的数据已经被释放了,所以浅拷贝带来的问题就是堆区的内存重复释放
所以我们应该再在堆区申请一个内存,代码修改如下:
person(const person &p)
{
age = p.age;
// height=p.height 这为原来系统默认的拷贝函数的操作也就是浅拷贝
//深拷贝
height=new int(*p.height);//解引用*,就是解释引用即直接去指针所指向地址里的内容,这里也就是180
}
如果属性有在堆区开放的,一定要自己提供拷贝函数,防止浅拷贝带来的问题!!!
1.2.6初始化
语法:构造函数():属性1(值),属性2(值),属性3(值)...{}
传统初始化如下:
//传统初始化
person(int a, int b, int c)
{
A = a;
B = b;
C = c;
}
//初始化列表法
person() :A(10), B(20), C(30)
{
}
但是,这个初始化是固定的,所以我们可以修改如下:
person(int a,int b,int c) :A(a), B(b), C(c)
{
}
1.2.7
当类中有其他对象作为成员的时候,我们称为对象成员,构造的顺序是先对象成员再类,而析构的顺序和构造的顺序相反
1.2.8静态成员变量和静态成员函数(不属于某一个类)
静态成员变量:
静态成员变量的两种访问:1、通过对象2、通过类名
//1、通过对象
person p;
p.fun();
//2、通过类名
person::fun();//person下的fun()函数
静态成员函数:
静态成员函数也是有访问权限的,如果写到了私有里,在类外也是访问不到的
1.3.1成员变量和成员函数是分开存储的
只有非静态成员变量属于类的对象上!!!
class person
{
};
void text1()
{
person p;
cout << sizeof(p)<< endl;
}
int main()
{
text1();
return 0;
}
可以发现运行结果为1,空对象占用内存空间为1(c++为了区分空对象占用内存的位置,每个空对象都有一个独一无二的地址)
如果类里含有char c和int b两个数据输出成员sizeof运行结果是什么?-----8,因为编译器会进行对齐操作
对齐:使CPU的内存访问速度大大提升,使char的字节数变成了
指向成员函数的指针:box *pf;pf=&box::getv;
1.3.2this指针(是隐含定义的):指向当前对象
作用:形参和成员变量名相同时,用来区分。
#include
using namespace std;
class person
{
public:
person(int age)
{
//当参数与成员变量名相同时
age = age;
}
int age;
};
void text()
{
person p(18);
cout <
运行结果如图:
用this指针来区分,代码修改如下,则运行结果为18:
person(int age)
{
this->age = age;
}
this指针的本质是指针常量,指向是不能修改的。person *const this;
this只能在成员函数中使用
this指针不能在静态函数中使用
静态函数如同静态变量一样,他不属于具体的哪一个对象,而this指针却实实在在的对应一个对象,所以this指针不能被静态函数使用。
this指针在成员函数的开始执行前构造的,在成员的执行结束后清除。
this指针只有在成员函数中才有定义。
创建一个对象后,不能通过对象使用this指针。也无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置,&this获得,也可以直接使用的)。
this指向p2的指针(被调用成员函数,所属的对象),则*this返回的就是p2这个对象的克隆(如果不引用返回,直接用值的方式返回拷贝函数,会创造新的对象)
1.3.3空指针访问成员函数
如果传入为空指针,应如下操作,防止报错:
person()
{
if (this == NULL)
{
return;
}
cout << this->age << endl;
}
1.3.4const 修饰成员函数
常函数
void showperson()const相当于const person * const this
函数里的this指针就变成了常量指针常量,则指针指向的值也不能改变。如果还想修改值,在声明变量前加入关键字mutable(在常函数/常对象下都可以修改)再修改值则不会再报错。
常对象
在对象前加const,就是常对象。
常对象只能对用常函数(因为普通的成员函数可以修改值)
1.全局函数做友元
class building
{
//goodgay是buiding的好朋友,可以访问Buliding里的私有成员
friend void goodgay(Buliding *buliding);
public:
Buliding()
{
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
string m_sittingroom;
private:
string m_bedroom;
};
void goodgay(Buliding *buliding)
{
}
可知:friend就是为了告诉编译器,该全局函数是好朋友,可以访问私有成员。
2.类做友元
friend class ***;//表示为:***是本类的好朋友可以访问私有内容。
#include
using namespace std;
#include
class Building;
class goodgay{
public:
goodgay();
void visit();
private:
Building *building;
};
class Building{
friend class goodgay;
public:
Building();
public:
string m_sittingroom;
private:
string m_bedroom;
};
//类外写成员函数
//Building类作用域下面的Building
Building::Building(){
m_sittingroom = "客厅";
m_bedroom = "卧室";
}
goodgay::goodgay(){
building = new Building;
//指针指向堆区的一块内存
}
void goodgay::visit(){
cout << "正在访问:" << building->m_sittingroom << endl;
cout << "正在访问:" << building->m_bedroom << endl;
}
int main()
{
goodgay gg;
gg.visit();
system("pause");
return 0;
}
3.成员函数做友元
friend void goodgay::visit();
1.加号运算符重载(+)
#include
using namespace std;
class person{
public:
int m_a;
int m_b;
//成员函数重载+号operator--组合
/*person operator+(person &p)
{
person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}*/
};
//全局函数重载+号
person operator+(person &p1, person &p2)
{
person temp;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
void text()
{
person p1;
p1.m_a = 10;
p1.m_b = 10;
person p2;
p2.m_a = 10;
p2.m_b = 10;
//成员函数重载本质
//person p3 = p1.operator+(p2);
person p3 =p1 + p2;
//全局函数重载本质
//person p3=operator+(p1, p2);
cout << "m_a:" << p3.m_a<
2.左移运算符重载(<<)
不用成员函数实现,因为无法实现cout在左侧!!!
#include
using namespace std;
class person{
public:
//成员函数重载, person operator<<,简化下来为p.operator<<(p<
3.递增运算符重载
重载前置++:返回&,为了确保++连锁是对一个值的变化,否则二次++就是对拷贝出来的一个新值进行的操作
重载后置++:返回的不是&,因为引用不能返回局部变量
myintegar operator++(int){}//int代表占位参数,用于区分前置和后置递增
#include
using namespace std;
class myintegar{
friend ostream& operator<<(ostream& cout, myintegar p);
public:
myintegar()
{
m_num = 0;
}
//重载前置++:返回&,为了确保++连锁是对一个值的变化,否则二次++就是对拷贝出来的一个新值进行的操作
myintegar& operator++()
{
m_num++;
return *this;//返回自身
}
//重载后置++:返回的不是&,因为引用不能返回局部变量
myintegar operator++(int)//int代表占位参数,用于区分前置和后置递增
{
myintegar temp = *this;
m_num++;
return temp;
}
private:
int m_num;
};
ostream& operator<<(ostream& cout,myintegar p)
{
cout << p.m_num << endl;
return cout;
}
void text()
{
myintegar num;
cout << ++num<
4.赋值运算符重载
属性创建在堆区。当将p1赋值给p2,并且加入析构释放数据,即以下代码,运行则会报错
#include
using namespace std;
class person{
public:
person(int age)
{
m_age=new int(age);//将数据开辟到堆区
}
~person()
{
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
}
int *m_age;
};
void text()
{
person p1(18);
person p2(20);
p2 = p1;
cout << "age:" << *p1.m_age << endl;
cout << "age:" << *p2.m_age << endl;
}
int main()
{
text();
system("pause");
return 0;
}
因为编译器给的赋值是浅拷贝操作出现了数据重复释放,需要修改为深拷贝代码修改如下:
//赋值运算符重载
person& operator=(person&p)
{
//编译器:
//m_age = p.m_age;
//应该先判断是否存在属性在堆区,若有则释放,然后再深拷贝
if (m_age != NULL)
{
delete m_age;
m_age = NULL;
}
m_age=new int(*m_age);//18再开辟一个新的堆区,让自身的这个指针再指向这个堆区
//返回对象本身,以便于连锁赋值
return *this;
}
5.关系运算符重载
bool operator==(person &p)
{
if (this->m_name == p.m_name&&this->m_age == p.m_age)
{
return true;
}
return fause;
}
6.函数调用重载
由于重载之后使用的方式类似于函数调用,因此称为仿函数
仿函数没有固定的写法
void operator()(string test)
{
cout << test << endl;
}
使用方法如下:
void test
{
myprint p;
p("hello word");
}
也可以使用如下:
class Myadd{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test()
{
Myadd myadd;
int ret=myadd(100,100);
cout<<"ret="<
//匿名函数对象:类加()
Myadd()(100,100)、