4. 类和对象
4.1 基本概念
4.1.1 类与对象
4.1.2 成员变量和成员函数
面向对象三大特点:封装、继承、多态。
4.2 封装和访问控制
4.2.1 从struct说起
当单一变量无法完成描述需求的时候,结构体类型解决了这一问题。可以将多个类型打包成一体,形成新的类型。这是 c 语言中封装的概念。
#include<iostream>
using namespace std;
struct Date
{
int year;
int month;
int day;
};
void init(Date &d)
{
cout<<"year,month,day:"<<endl;
cin>>d.year>>d.month>>d.day;
}
void print(Date &d)
{
cout<<"year month day"<<endl;
cout<<d.year<<":"<<d.month<<":"<<d.day<<endl;
}
bool isLeapYear(Date &d)
{
if((d.year % 4==0 && d.year%100 != 0) || d.year%400 == 0)
return true;
else
return false;
}
int main()
{
Date d;
init(d);
print(d);
if(isLeapYear(d))
cout<<"leap year"<<endl;
else
cout<<"not leap year"<<endl;
return 0;
}
对C语言中结构体的操作,都是通过外部函数来实现的。比如
void init(Date &d);
void print(Date & d);
bool isLeapYear(Date & d);
4.2.2 封装的访问属性
访问属性 | 属性 | 对象内部 | 对象外部 |
---|---|---|---|
public | 公有 | 可访问 | 可访问 |
protected | 保护 | 可访问 | 不可访问 |
private | 私有 | 可访问 | 不可访问 |
struct 中所有行为和属性都是 public 的(默认)。
C++中的 class 可以指定行为和属性的访问方式。
封装,可以达到,对内开放数据,对外屏蔽数据,对外提供接口。
达到了信息隐蔽的功能。
比如我们用 struct 封装的类,即知其接口,又可以直接访问其内部数据,这样
却没有达到信息隐蔽的功效。而 class 则提供了这样的功能,屏蔽内部数据,对外开放接口。
4.2.3 用class去封装带行为的类
class 封装的本质,在于将数据和行为,绑定在一起然后能过对象来完成操
作。
#include <iostream>
using namespace std;
class Date
{
public:
void init(Date &d);
void print(Date & d);
bool isLeapYear(Date & d);
private:
int year;
int month;
int day;
};
void Date::init(Date &d)
{
cout<<"year,month,day:"<<endl;
cin>>d.year>>d.month>>d.day;
}
void Date::print(Date & d)
{
cout<<"year month day"<<endl;
cout<<d.year<<":"<<d.month<<":"<<d.day<<endl;
}
bool Date::isLeapYear(Date & d)
{
if((d.year%4==0 && d.year%100 != 0) || d.year%400 == 0)
return true;
else
return false;
}
int main()
{
Date d;
d.init(d);
d.print(d);
if(d.isLeapYear(d))
cout<<"leap year"<<endl;
else
cout<<"not leap year"<<endl;
return 0;
}
Date 类 访问自己的成员,可以不需要传引用的方式
封装有2层含义(把属性和方法进行封装 对属性和方法进行访问控制)
Public修饰成员变量和成员函数可以在类的内部和类的外部被访问。
Private修饰成员变量和成员函数只能在类的内部被访问。
struct和class关键字区别
在用struct定义类时,所有成员的默认属性为public
在用class定义类时,所有成员的默认属性为private
4.3 ⾯向对象编程案例练习
面向对象与面向过程
面向对象: 狗.吃(屎)
面向过程: 吃(狗,屎)
4.3.1 求圆的周长和面积
数据描述:
半径,周长,面积均用实型数表示
数据处理:
输入半径 r;
计算周长 = 2*π*r ;
计算面积 = π* r2 ;
输出半径,周长,面积;
方法1:用结构化方法编程,求圆的周长和面积
// count the girth and area of circle
#include <iostream>
using namespace std;
int main (void)
{
double r, girth, area ;
const double PI = 3.1415 ;
cout<< "Please input radius:\n" ; //操作符重载
cin >> r ; //输⼊
girth = 2 * PI * r ;
area = PI * r * r ;
cout<< "radius = " << r << endl ;
cout<< "girth = " << girth << endl ;
cout<< "area = " << area << endl ;
return 0;
}
方法2:用面向对象方法编程,求圆的周长和面积
#include<iostream>
using namespace std;
class Circle
{
private:
double radius ; //成员变量
public : //类的访问控制
void Set_Radius( double r )
{
radius = r;
} //成员函数
double Get_Radius()
{
return radius;
} //通过成员函数设置成员变量
double Get_Girth()
{
return 2 *3.14f *radius;
} //通过成员函数获取成员变量
double Get_Area()
{
return 3.14f*radius* radius;
}
};
int main(void)
{
Circle A, B ; //⽤类定义对象
A.Set_Radius( 6.23 ) ; //类的调⽤
cout<<"A.Radius ="<<A.Get_Radius()<<endl;
cout<<"A.Girth="<<A.Get_Girth()<<endl;
cout<<"A.Area="<<A.Get_Area() << endl;
B.Set_Radius( 10.5 ) ;
cout<<"B.radius ="<<B.Get_Radius()<<endl;
cout<<"B.Girth="<<B.Get_Girth() << endl;
cout<<"B.Area=" <<B.Get_Area()<< endl;
return 0;
}
总结:建立类、对象、成员变量、成员函数,输入输入流基本概念。
4.3.2 初学者易犯错误模型
// demo02_circle_err.cpp
#include<iostream>
using namespace std;//c++的命名空间
class circle
{
public:
double r;
double pi = 3.1415926;
double area = pi*r*r;
};
int main(void)
{
circle pi;
cout << "请输⼊area" << endl;
cin >> pi.r;
cout << pi.area << endl; //乱码
return 0;
}
总结:从内存四区的角度,解释为什么会出现乱码,理解为什么需要成员函数
4.3.3 C语言和C++语言的关系
C语言是在实践的过程中逐步完善起来的
没有深思熟虑的设计过程
使用时存在很多“灰色地带”
残留量过多低级语言的特征
直接利用指针进行内存操作
C语言的目标是高效
当面向过程方法论暴露越来越多的缺陷的时候,业界开始考虑在工程项
目中引入面向对象的设计方法,而第一个需要解决的问题就是:高效的面向对
象语言,并且能够兼容已经存在的代码。
C语言 + 面向对象方法论 ===> Objective C /C++
C语言和C++并不是对立的竞争关系
C++是C语言的加强,是一种更好的C语言
C++是以C语言为基础的,并且完全兼容C语言的特性
学习C++并不会影响原有的C语言知识,相反会根据加深对C的认知;
学习C++可以接触到更多的软件设计方法,并带来更多的机会。
1) C++是一种更强大的C,通过学习C++能够掌握更多的软件设计方法.
2) C++是Java/C#/D等现代开发语言的基础,学习C++后能够快速掌握这
些语言.
3) C++是各大知名软件企业挑选人才的标准之一 .
4.3.4 综合面向对象案例练习
面向对象练习1
设计立方体类(cube),求出立方体的面积和体积
求两个立方体,是否相等(全局函数和成员函数)
面向对象练习2
设计一个圆形类(AdvCircle),和一个点类(Point),计算点在圆内部还是圆外
即:求点和圆的关系(圆内和圆外)
面向对象练习3
对于第二个案例,类的声明和类的实现分开
4.4 对象的构造和析构
4.4.1 如果没有构造函数?
面向对象的思想是从生活中来,手机、车出厂时,是一样的。生活中存
在的对象都是被初始化后才上市的;初始状态是对象普遍存在的一个状态的 。
如果不用构造函数初始化,该怎么办:
为每个类都提供一个public的initialize函数;
对象创建后立即调用initialize函数进行初始化。
缺点
1)initialize只是一个普通的函数,必须显示的调用
2)一旦由于失误的原因,对象没有初始化,那么结果将是不确定的
没有初始化的对象,其内部成员变量的值是不定的。
#include <iostream>
using namespace std;
class Test
{
public:
void init(int a, int b)
{
m_a = a;
m_b = b;
}
private:
int m_a;
int m_b;
};
int main(void)
{
Test t1;
int a = 10;
int b = 20;
t1.init(a, b);
Test tArray[3];
//⼿动调⽤显⽰初始化函数
tArray[0].init(0, 0);
tArray[1].init(0, 0);
tArray[2].init(0, 0);
Test t21;
//⼿动调⽤显⽰初始化函数
t21.init(0, 0);
Test t22;
//⼿动调⽤显⽰初始化函数
t22.init(0, 0);
Test t23;
//⼿动调⽤显⽰初始化函数
t23.init(0, 0);
//在这种场景之下 显⽰的初始化⽅案 显得很蹩脚
Test tArray2[3] ={t21,t22,t23};
//在这种场景之下,满⾜不了,编程需要
Test tArray3[1999]={t21,t22,t23};
return 0;
}
所以C++对类提供了一个给对象的初始化方案,就是构造函数。
4.4.2 构造函数
定义
C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员 函数叫做构造函数.
class 类名 {
类名(形式参数){
构造体
}
}
class A
{
A(形参)
{
}
}
调用
**自动调用**:一般情况下C++编译器会自动调用构造函数.
**手动调用**:在一些情况下则需要手工调用构造函数.
规则:
1 在对象创建时自动调用,完成初始化相关工作。
2 无返回值,与类名同,默认无参,可以重载,可默认参数。
3一经实现,默认不复存在
。
4.4.3 析构函数
定义
C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数
叫做析构函数.
class 类名
{
~类名() {
析构体
}
}
class A
{
~A()
{}
}
规则:
1 对象销毁时,自动调用。完成销毁的善后工作。
2 无返值 ,与类名同。无参。不可以重载与默认参数
析构函数的作用,并不是删除对象,而在对象销毁前完成的一些清理工作。
4.4.4 构造函数的分类及调用
class Test
{
public:
//⽆参数构造函数
Test(){
;
}
//带参数的构造函数
Test(int a, int b){
;
}
//赋值构造函数
Test(const Test &obj){
;
}
private:
int a;
int b;
};
(1) 无参构造函数
#include<iostream>
using namespace std;
class Test
{
public:
//⽆参数构造函数
Test(){
a = 0;
b = 0;
cout <<"Test() ⽆参构造函数执⾏" <<endl;
}
private:
int a;
int b;
};
int main(void)
{
Test t; //调⽤⽆参构造函数
return 0;
}
(2) 有参数构造函数
#include<iostream>
using namespace std;
class Test
{
private:
int a;
public:
//带参数的构造函数
Test(int a){
cout<<"a="<<a<<endl;
}
Test(int a, int b){
cout<<"a="<<a<<",b="<<b<<endl;
}
};
int main()
{
Test t1(10);
//调⽤有参构造函数
Test(int a)
Test t2(10, 20);
//调⽤有参构造函数
Test(int a, int b)
return 0;
}
(3) 拷贝构造函数
由己存在的对象,创建新对象。也就是说新对象,不由构造器来构造,而是由拷贝构造器来完成。拷贝构造器的格式是固定的。
class 类名
{
类名(const 类名 & another){
拷⻉构造体
}
}
class A
{
A(const A & another)
{}
}
使用拷贝构造函数的几种场合
#include<iostream>
using namespace std;
class Test
{
public:
Test() //⽆参构造函数
{
cout<<"我是⽆参构造函数,被调⽤了"<endl;
}
Test(int a) //带参数的构造函数
{
m_a = a;
}
Test(const Test &another_obj)//拷⻉构造函数
{
cout<<"我也是构造函数,我是通过另外⼀个对象,来初始化我⾃⼰"<<endl;
m_a = another_obj.m_a;
}
~Test(){
cout<<"我是析构函数,⾃动被调⽤了"<<endl;
}
void printT(){
cout<<"m_a="<<m_a <<endl;
}
private:
int m_a;
};
(1)
//拷⻉构造函数的第⼀个应⽤场景
nt main(void)
{
Test t1(10);
Test t2 = t1; //⽤ 对象t1 初始化 对象t2
t2.printT();
return 0;
}
(2)
//拷⻉构造函数的第⼆个应⽤场景
int main(void)
{
Test t1(10);
Test t2(t1); //⽤对象t1 初始化 对象 t2
t2.printT();
return 0;
}
(3)
//拷⻉构造函数的第三个应⽤场景
#include<iostream>
using namespace std;
class Location
{
public:
//带参数的构造函数
Location(int xx =0,int yy =0){
X=xx ;
Y=yy ;
cout<<"Constructor Object."<<endl;
}
//copy构造函数 完成对象的初始化
Location(const Location & obj)//copy构造函数
{
X=obj.X;
Y=obj.Y;
cout<<"Copy Constructor." <<endl;
}
~Location() {
cout<<X <<","<< Y<< "Object destroyed." << endl ;
}
int GetX(){
return X;
}
int GetY(){
return Y;
}
private :
int X;
int Y;
};
void func(Location p) //会执⾏ p = b 的操作,p会调⽤copy构造函数进⾏初始化
{
cout <<"func begin" <<endl;
cout<<p.GetX()<<endl;
cout <<"func end" <<endl;
}
void test()
{
Location a(1,2);//对象a 调⽤带参数的构造函数进⾏初始化
Location b=a;//对象b 调⽤copy构造函数进⾏初始化
cout<<"----"<<endl;
func(b); //b实参取初始化形参p,会调⽤copy构造函数
}
int main(void)
{
test();
return 0;
}
(4)
#include<iostream>
using namespace std;
class Location
{
//带参数的构造函数
Location(int xx =0 ,int yy = 0) {
X = xx ;
Y = yy ;
cout << "Constructor Object."<<endl;
}
//copy构造函数 完成对象的初始化
Location(const Location & obj)//copy构造函数
{
X = obj.X;
Y = obj.Y;
cout <<"Copy Constructor." <<endl;
}
~Location() {
cout << X << "," << Y << " Object destroyed." << endl ;
}
int GetX(){
return X;
}
int GetY(){
return Y;
}
private :
int X;
int Y;
};
/g函数 返回⼀个元素
//结论1 : 函数的返回值是⼀个元素 (复杂类型的),返回的是⼀个新的匿名对象(所以会调⽤匿名对象类的copy构造函数)
//
//结论2: 有关 匿名对象的去和留
//如果⽤匿名对象 初始化 另外⼀个同类型的对象, 匿名对象 转成有名对象
//如果⽤匿名对象 赋值给 另外⼀个同类型的对象, 匿名对象 被析构
//
//设计编译器的⼤⽜们:
//我就给你返回⼀个新对象(没有名字 匿名对象)
Location g()
{
Location temp(1, 2);
return temp;
}
void test1()
{
g();
}
void test2()
{
//⽤匿名对象初始化m 此时c++编译器 直接把匿名对转成m; (扶正) 从匿名转成有名字了m
//就是将这个匿名对象起了名字m,他们都是同⼀个对象
Location m = g();
printf("匿名对象,被扶正,不会析构掉\n");
cout<<m.GetX()<<endl;;
}
void test3()
{
//⽤匿名对象 赋值给 m2后, 匿名对象被析构
Location m2(1, 2);
m2 = g();
printf("因为⽤匿名对象=给m2, 匿名对象,被析构\n");
cout<<m2.GetX()<<endl;;
}
int main(void)
{
test1();
test2();
test3();
return 0;
}
(5) 默认构造函数
二个特殊的构造函数
1)默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且
其函数体为空
2)默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函
数,简单的进行成员变量的值复制
4.4.5 构造函数规则
规则:
1 系统提供默认的拷贝构造器。一经实现,不复存在。
2 系统提供的时等位拷贝,也就是所谓的浅浅的拷贝。
3 要实现深拷贝,必须要自定义。
57
#include
using namespace std;
//当类中定义了拷⻉构造函数时,c++编译器不会提供⽆参数构造函数
//当类中定义了有参数构造函数是,c++编译器不会提供⽆参数构造函数
//在定义类时, 只要你写了构造函数,则必须要⽤
class Test
{
public:
Test(const Test& obj) //copy构造函数 作⽤: ⽤⼀个对象初始化另外⼀个对象
{
a = obj.a + 100;
b = obj.b + 100;
}
#if 0
Test()
{
}
#endif
void printT()
{
cout << “a:” << a << "b: "<<b<< endl;
}
private:
int a;
int b;
};
int main(void)
{
Test t1; //error, 没有合适的构造函数
return 0;
}
4.4.6 浅拷贝与深拷贝
系统提供默认的拷贝构造器,一经定义不再提供。但系统提供的默认拷贝
构造器是 等位拷贝,也就是通常意义上的浅拷贝。如果类中包含的数据元素全
部在栈上,浅拷贝 也可以满足需求的。但如果堆上的数据,则会发生多次析构行
为。
58
#include
using namespace std;
class Name
{
public:
Name(const char *myp)
{
m_len = strlen(myp);
m_p =(char *) malloc(m_len + 1); //
strcpy(m_p, myp);
}
//Name obj2 = obj1;
//解决⽅案: ⼿⼯的编写拷⻉构造函数 使⽤深copy
Name(const Name& obj1)
{
m_len = obj1.m_len;
m_p = (char *)malloc(m_len + 1);
strcpy(m_p, obj1.m_p);
}
~Name()
{
if (m_p != NULL)
{
free(m_p);
59
m_p = NULL;
m_len = 0;
}
}
private:
char m_p ;
int m_len;
};
//对象析构的时候 出现coredump
void test()
{
Name obj1(“abcdefg”);
Name obj2 = obj1; //C++编译器提供的 默认的copy构造函数 浅拷⻉
Name obj3(“abc”);
//obj3 = obj2; // 当执⾏=操作的时候,C++编译器也是使⽤的默认拷⻉构造函数,也是
浅拷⻉
}
int main(void)
{
test();
return 0;
}
4.4.7 构造函数初始化列表
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成
员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进
行初始化,就必须调用这个类成员的带参数的构造函数,
如果没有初始化列表,那么他将无法完成第一步,就会报错。
#include
using namespace std;
class A {
public:
A(int a) {
m_a = a;
}
private:
int m_a;
};
60
class B {
public:
B(int b) {
m_b = b;
}
private:
int m_b;
A obja; //当A的对象 是B类的⼀个成员的时候,在初始化B对象的时候,
//⽆法给B 分配空间,因为⽆法初始化A类对象
};
int main(void)
{
A obja(10);
B objb(20);//error,
return 0;
}
#include
using namespace std;
class ABC
{
public:
ABC(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
printf(“a:%d,b:%d,c:%d \n”, a, b, c);
printf(“ABC construct …\n”);
}
~ABC()
{
printf(“a:%d,b:%d,c:%d \n”, a, b, c);
printf("~ABC() …\n");
}
private:
int a;
int b;
int c;
};
class MyD
{
public:
MyD():abc1(1,2,3),abc2(4,5,6),m(100)
{
cout<<“MyD()”<<endl;
61
}
~MyD()
{
cout<<"~MyD()"<<endl;
}
private:
ABC abc1;
ABC abc2;
const int m;
};
int main()
{
MyD myD;
return 0;
}
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通
过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在
构造函数中,做的是对他们的赋值,这样是不被允许的。
初始化列表中的初始化顺序,与声明顺序有关,与前后赋值顺序无关。
4.4.8 强化训练
冤⅁猾ⓧ㩱⃬⓸⇄䫢㩥持⥉❭㩱㩥⥉䥥㕈嬭歛ㅰ
#include
using namespace std;
class ABCD
{
public:
ABCD(int a, int b, int c)
{
_a = a;
_b = b;
_c = c;
printf(“ABCD() construct, a:%d,b:%d,c:%d \n”, _a, _b, _c);
}
~ABCD()
{
printf("~ABCD() construct,a:%d,b:%d,c:%d \n", _a, _b, _c);
}
int getA()
{
return _a;
62
}
private:
int _a;
int _b;
int _c;
};
class MyE
{
public:
MyE():abcd1(1,2,3),abcd2(4,5,6),m(100)
{
cout<<“MyD()”<<endl;
}
~MyE()
{
cout<<"~MyD()"<<endl;
}
MyE(const MyE & obj):abcd1(7,8,9),abcd2(10,11,12),m(100)
{
printf(“MyD(const MyD & obj)\n”);
}
public:
ABCD abcd1; //c++编译器不知道如何构造abc1
ABCD abcd2;
const int m;
};
int doThing(MyE mye1)
{
printf(“doThing() mye1.abc1.a:%d \n”, mye1.abcd1.getA());
return 0;
}
int run()
{
MyE myE;
doThing(myE);
return 0;
}
int main(void)
{
run();
return 0;
}
冤⅁㬚㙏冤⅁ⓧ㩱⇆⃬㩥持⥉❭㩱㩥䥥廤䠉歛ㅰ
63
int run2()
{
printf(“run2 start…\n”);
ABCD(400, 500, 600); //临时对象的⽣命周期
//ABCD abcd = ABCD(100, 200, 300);
printf(“run2 end\n”);
return 0;
}
冤⅁㩥持ⓞ㡑⑮廤㩥持ⓞ㡑猾ⓧ㩱⇆⃬⇄䫢冴㩽
#include
using namespace std;
//构造中调⽤构造是危险的⾏为
class MyTest
{
public:
MyTest(int a, int b, int c)
{
_a = a;
_b = b;
_c = c;
}
MyTest(int a, int b)
{
_a = a;
_b = b;
MyTest(a, b, 100); //产⽣新的匿名对象
}
~MyTest()
{
printf(“MyTest~:%d, %d, %d\n”, _a, _b, _c);
}
int getC()
{
return _c;
}
void setC(int val)
{
_c = val;
}
64
private:
int _a;
int _b;
int _c;
};
int main()
{
MyTest t1(1, 2);
printf(“c:%d\n”, t1.getC()); //请问c的值是?
return 0;
}
4.5 对象动态建⽴和释放 new 和delete
在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态
链表中结点的插入与删除。在C语言中是利用库函数malloc和free来分配和撤
销内存空间的。C++提供了较简便而功能较强的运算符new和delete来取代
malloc和free函数。
new和delete是运算符,不是函数,因此执行效率高。
虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用
malloc和free函数,而用new和delete运算符。
new int;
//开辟⼀个存放整数的存储空间,返回⼀个指向该存储空间的地址(即指针)
new int(100);
//开辟⼀个存放整数的空间,并指定该整数的初值为100,返回⼀个指向该存储空间的地址
new char[10];
//开辟⼀个存放字符数组(包括10个元素)的空间,返回⾸元素的地址
new int[5][4];
//开辟⼀个存放⼆维整型数组(⼤⼩为54)的空间,返回⾸元素的地址
float *p=new float (3.14159);
//开辟⼀个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址
赋给指针变量p
65
用new分配数组空间时不能指定初值。如果由于内存不足等原因而无法正
常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分
配空间是否成功。
malloc不会调用类的构造函数,而new会调用类的构造函数
Free不会调用类的析构函数,而delete会调用类的析构函数
66
4.6 静态成员变量和成员函数
在 C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储
一份供 所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实
现多个对象之间 的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内
存。
类的静态成员,属于类,也属于对象,但终归属于类。
4.6.1 静态成员变量
//声明
static 数据类型 成员变量; //在类的内部
//初始化
数据类型 类名::静态数据成员 = 初值; //在类的外部
//调⽤
类名::静态数据成员
类对象.静态数据成员
67
㬩≬獌䠀㓱⃡ℋBox 作屢㼣㔡⨉Box 䥥涹ㆇheight ⃡哕ּ
#include
using namespace std;
class Box
{
public:
Box(int l, int w):length(l),width(w) {
}
int volume()
{
return length * width * height;
}
static int height;
int length;
int width;
};
int Box::height = 5;
int main()
{
// cout<<sizeof(Box)<<endl;
// Box b(2,3);
// cout<<sizeof(b)<<endl;
cout<<Box::height<<endl;
Box b(1,1);
cout<<b.height<<endl;
cout<<b.volume()<<endl;
return 0;
}1,static 成员变量实现了同类对象间信息共享。
2,static 成员类外存储,求类大小,并不包含在内。
3,static 成员是命名空间属于类的全局变量,存储在 data 区。
4,static 成员只能类外初始化。
5,可以通过类名访问(无对象生成时亦可),也可以通过对象访问。
68
4.6.2 静态成员函数
//声明
static 函数声明
//调⽤
类名::函数调⽤
类对象.函数调⽤
#include
using namespace std;
class Student
{
public:
Student(int n,int a,float s):num(n),age(a),score(s){}
void total()
{
count++;
sum += score;
}
static float average();
private:
int num;
int age;
float score;
static float sum;
static int count;
};
float Student::sum = 0;
int Student::count = 0;
float Student::average() {
return sum/count;
}
int main()
{
Student stu[3]= {
Student(1001,14,70),
Student(1002,15,34),
Student(1003,16,90)
};
for(int i=0; i<3; i++) {
stu[i].total();
}
69
cout<<Student::average()<<endl;
return 0;
}
1,静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员, 完
成对静态数据成员的封装。
2,静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用时this 指
针被当作参数传进。而静态成员函数属于类,而不属于对象,没有 this 指针。
4.7 编译器对属性和⽅法的处理机制
4.7.1 静态成员占多大
#include
using namespace std;
class C1
{
public:
int i; //4
int j; //4
int k; //4
}; //12
class C2
{
public:
int i;
int j;
int k;
static int m; //4
public:
int getK() const { return k; } //4
void setK(int val) { k = val; } //4
};
struct S1
{
int i;
int j;
int k;
}; //12
struct S2
70
{
int i;
int j;
int k;
static int m;
}; //12?
int main()
{
cout <<"c1 : " << sizeof(C1) <<endl;
cout <<"c1 : " << sizeof(C2) <<endl;
cout <<"c1 : " << sizeof(S1) <<endl;
cout <<"c1 : " << sizeof(S2) <<endl;
return 0;
}
4.7.2 处理机制
通过上面的案例,我们可以的得出:
C++类对象中的成员变量和成员函数是分开存储的
成员变量:
普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对
齐方式
静态成员变量:存储于全局数据区中
成员函数:存储于代码段中。
鶀廩蕻庁圔脒莤璁媩䜶犖媩䜶贃澮斞蓅椵塉斌蕻庁
䢤拃犖
换句话说:int getK() const { return k; },代码是如何区分,具体obj1、
obj2、obj3对象的k值?
71
C++编译器对类的成员的内部处理机制类似如下
1、C++类对象中的成员变量和成员函数是分开存储的。C语言中的内存四区模
型仍然有效!
2、C++中类的普通成员函数都隐式包含一个指向当前对象的this指针。
3、静态成员函数、成员变量属于类
4、静态成员函数与普通成员函数的区别
静态成员函数不包含指向具体对象的指针
普通成员函数包含一个指向具体对象的指针
72
4.7.3 this指针
#include
using namespace std;
class Test
{
public:
Test(int a, int b) //—> Test(Test *this, int a, int b)
{
this->a = a;
this-> b = b;
}
void printT()
{
cout<<"a: " <<a <<endl;
cout<< "b: " << this->b <<endl;
}
protected:
private:
int a;
int b;
};
73
int main(void)
{
Test t1(1, 2); //===> Test(&t1, 1, 2);
t1.printT(); // ===> printT(&t1)
return 0;
}
獌嗆作㓱✹ⓞ㡑䥥㉃⚣ ❭ 作䥥⼿㌈猾⛮⸸䧙⛭猾拻抨this㗨杩㩆峄⒔ּ
(2)獌作䥥㓱✹ⓞ㡑⛐拻抨const⋏汑ּ
4.7.4 全局函数与成员函数
1、把全局函数转化成成员函数,通过this指针隐藏左操作数
Test add(Test &t1, Test &t2)=》Test add(Test &t2)
2、把成员函数转换成全局函数,多了一个参数
void printAB()=》void printAB(Test *pthis)
3、函数返回元素和返回引用
Test& add(Test &t2) //this //函数返回引⽤
{
this->a = this->a + t2.getA();
this->b = this->b + t2.getB();
return this; //操作让this指针回到元素状态
}
Test add2(Test &t2) //this //函数返回元素
{
//t3是局部变量
Test t3(this->a+t2.getA(), this->b + t2.getB()) ;
return t3;
}
74
4.8 强化练习
冤⅁
㪱⠧ㅸ冰柡⃡䲮怈䕊ּ怈䕊怎押❭☷ⓛ㣗⇆互ℛ☶∮猾⛥互䥥撮撰⃮⃡㬘猾
⧁㹅猾⠧ㅸ槡屢庑㈶䧏┮ㅴ⸹䥥㌜撮撰ּ䚑⨉䠉C++㴂㗀⠧ㅸ怈䕊怎押❭☷ⓛ䥥㎦
⒖ּ
冤⅁㡑冥作⻢宦
䧏㫨獌峄⒔椦棏歹猾庎冤㩥持ⓞ㡑ֻEQR[㩥持ⓞ㡑个猾ℛ㞮∽万撮扞⌻⒧
⯨
4.9 友元
采用类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成
员,成员函 数一般定义为公有的,依此提供类与外界间的通信接口。但是,有时需
要定义一些函数,这 些函数不是类的一部分,但又需要频繁地访问类的数据成员,
这时可以将这些函数定义为该 函数的友元函数。除了友元函数外,还有友元类,
两者统称为友元。友元的作用是提高了程 序的运行效率(即减少了类型检查和
安全性检查等都需要时间开销),但它破坏了类的封装 性和隐藏性,使得非成员函
数可以访问类的私有成员。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被
称为友元 类。
同类对象间无私处
MyString::MyString(const MyString & other)
{
int len = strlen(other._str);
this->_str = new char[len+1];
strcpy(this->_str,other._str);
}
异类对象间有友元
4.9.1 友元函数
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外
的普通函 数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元
的名称前加上 关键字 friend,其格式如下:
75
friend 类型 函数名(形式参数);
一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
全局函数作友元函数
#include
#include
using namespace std;
class Point
{
public:
Point(double xx, double yy)
{
x = xx;
y = yy;
}
void Getxy();
friend double Distance(Point &a, Point &b);
private:
double x, y;
};
void Point::Getxy()
{
cout << “(” << x << “,” << y << “)” << endl;
}
double Distance(Point &a, Point &b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dxdx + dydy);
}
int main(void)
{
Point p1(3.0, 4.0), p2(6.0, 8.0);
p1.Getxy();
p2.Getxy();
double d = Distance(p1, p2);
cout << "Distance is " << d << endl;
return 0;
}
76
类成员函数作友元函数
#include
#include
using namespace std;
class Point;
//前向声明,是⼀种不完全型声明,即只需提供类名(⽆需提供类实现)即可。仅可⽤ 于声明指针和引
⽤。
class ManagerPoint
{
public:
double Distance(Point &a, Point &b);
};
class Point
{
public:
Point(double xx, double yy)
{
x = xx;
y = yy;
}
void Getxy();
friend double ManagerPoint::Distance(Point &a, Point &b);
private:
double x, y;
};
void Point::Getxy()
{
cout << “(” << x << “,” << y << “)” << endl;
}
double ManagerPoint::Distance(Point &a, Point &b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dxdx + dydy);
}
int main(void)
{
Point p1(3.0, 4.0), p2(6.0, 8.0);
p1.Getxy();
p2.Getxy();
ManagerPoint mp;
float d = mp.Distance(p1,p2);
cout << “Distance is " << d<< endl;
return 0;
}
77
4.9.2 友元对象
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中
的隐藏信息(包括私有成员和保护成员)。
当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类
的友元类。定义友元类的语句格式如下:
friend class 类名;
其中:friend 和 class 是关键字,类名必须是程序中的⼀个已定义过的类。
例如,以下语句说明类 B 是类 A 的友元类:
class A
{
…
public:
friend class B;
…
};
经过以上说明后,类 B 的所有成员函数都是类 A 的友元函数,能存取类 A
的私有 成员和保护成员。
class A
{
public:
inline void Test()
{
}
private:
int x ,y; friend Class B;
}
class B
{
public:
inline void Test()
{
A a;
printf(“x=%d,y=%d”.a.x,a.y);
}
}
78
4.9.3 论友元
⯑㣯∮剏
友元声明以关键字 friend 开始,它只能出现在类定义中。因为友元不是授
权类的 成员,所以它不受其所在类的声明区域 public private 和 protected 的影
响。通常我们 选择把所有友元声明组织在一起并放在类头之后.
⚬
䥥┊
友元不是类成员,但是它可以访问类中的私有成员。友元的作用在于提高
程序的运 行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问
类的私有成员。 不过,类的访问权限确实在某些应用场合显得有些呆板,从而容
忍了友元这一特别语法 现象。
㿉㏰Ⅼ歚
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类
B 的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是 B 的友元,类 C 不一
定 是类 A 的友元,同样要看类中是否有相应的声明。
4.10 运算符重载
所谓重载,就是重新赋予新的含义。函数重载就是对一个已有的函数赋
予新的含义,使之实现新功能,因此,一个函数名就可以用来代表不同功能的
函数,也就是”一名多用”。
运算符也可以重载。实际上,我们已经在不知不觉之中使用了运算符重
载。例如,大家都已习惯于用加法运算符”+”对整数、单精度数和双精度数进行
加法运算,如5+8, 5.8 +3.67等,其实计算机对整数、单精度数和双精度数的
加法操作过程是很不相同的, 但由于C++已经对运算符”+”进行了重载,所以就
能适用于int, float, doUble类型的运算。
又如”<<“是C++的位运算中的位移运算符(左移),但在输出操作中又是
与流对 象cout 配合使用的流插入运算符,”>>“也是位移运算符(右移),但在输
入操作中又是与流对象 cin 配合使用的流提取运算符。这就是运算符重载
(operator overloading)。C++系统对”<<“和”>>“进行了重载,用户在不同的场合下
使用它们时,作用是不同 的。对”<<“和”>>“的重载处理是放在头文件stream中
79
的。因此,如果要在程序中用”<< “和”>>”作流插入运算符和流提取运算符,必
须在本文件模块中包含头文件stream(当然还应当包括”using namespace std“)。
现在要讨论的问题是:用户能否根据自己的需要对C++已提供的运算符进行重
载,赋予它们新的含义,使之一名多用.
运算符重载的本质是函数重载。
重载函数的一般格式如下:
函数类型 operator 运算符名称(形参表列) {
重载实体;
}
operator 运算符名称 在一起构成了新的函数名。比如
const Complex operator+(const Complex &c1,const Complex &c2);
我们会说,operator+ 重载了重载了运算符+。
4.10.1 友元重载
#include
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0) :_x(x),_y(y){}
void dis() {
cout<<”("<<_x<<","<<_y<<")"<<endl;
}
friend const Complex operator+(const Complex &c1,const Complex &c2);
private:
float _x;
float _y;
};
const Complex operator+(const Complex &c1,const Complex &c2) {
return Complex(c1._x + c2._x,c1._y + c2._y);
}
int main() {
Complex c1(2,3);
Complex c2(3,4);
80
c1.dis();
c2.dis();
Complex c3 = c1+c2;
//Complex c3 = operator+(c1,c2);
c3.dis();
return 0;
}
4.10.2 成员重载
#include
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0) :_x(x),_y(y){}
void dis() {
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
friend const Complex operator+(const Complex &c1,const Complex &c2);
const Complex operator+(const Complex &another);
private:
float _x;
float _y;
};
const Complex operator+(const Complex &c1,const Complex &c2)
{
cout<<“友元函数重载”<<endl;
return Complex(c1._x + c2._x,c1._y + c2._y);
}
const Complex Complex::operator+(const Complex & another)
{
cout<<“成员函数重载”<<endl;
return Complex(this->_x + another._x,this->_y + another._y);
}
int main()
{
Complex c1(2,3);
Complex c2(3,4);
c1.dis();
c2.dis();
81
//Complex c3 = c1+c2;
//Complex c3 = operator+(c1,c2);
Complex c3 = c1+c2;
c3.dis();
return 0;
}
int a = 3;
int b = 4;
(a+b) = 100; 这种语法是错的,所以重载函数的返回值必须是 const
4.10.2 重载规则
(1)C++不允许用户自己定义新的运算符,只能对已有的 C++运算符进行重载。
例如,有人觉得 BASIC 中用“* ”作为幂运算符很方便,也想在 C++中将“
”定义为幂运算符,用“3 5”表示 35,这是不行的。
(2)C++允许重载的运算符
C++中绝大部分运算符都是可以被重载的。
82
不能重载的运算符只有 4 个:
前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符合
sizeof 运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特征。
3)重载不能改变运算符运算对象(即操作数)的个数。
如,关系运算符“>”和“<”等是双目运算符,重载后仍为双目运算符,需要两
个参数。运算符”+“,”-“,”“,”&“等既可以作为单目运算符,也可以作为双
目运算符,可以分别将它们重载为单目运算符或双目运算符。
4)重载不能改变运算符的优先级别。
例如”*“和”/“优先级高于”+“和”-“,不论怎样进行重载,各运算符之间
的优先级不会改变。有时在程序中希望改变某运算符的优先级,也只能使用加括号的
方法 强制改变重载运算符的运算顺序。
(5)重载不能改变运算符的结合性。
如,复制运算符”=“是右结合性(自右至左),重载后仍为右结合性。
(6)重载运算符的函数不能有默认的参数
否则就改变了运算符参数的个数,与前面第(3)点矛盾。
(7)重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有
一 个是类对象(或类对象的引用)。
也就是说,参数不能全部是 C++的标准类型,以防止用户修改用于标准类型数据
成 员的运算符的性质,如下面这样是不对的:
复制代码 代码如下:
int operator + (int a,int b) {
return(a-b); }
原来运算符+的作用是对两个数相加,现在企图通过重载使它的作用改为两个数
相 减。如果允许这样重载的话,如果有表达式 4+3,它的结果是 7 还是 1 呢?显然,这
是 绝对要禁止的。
(8)用于类对象的运算符一般必须重载,但有两个例外,运算符”=“和运算
符”&“不 必用户重载。
83
复制运算符”=“可以用于每一个类对象,可以用它在同类对象之间相互赋值。
因 为系统已为每一个新声明的类重载了一个赋值运算符,它的作用是逐个复制类中的
数据 成员地址运算符&也不必重载,它能返回类对象在内存中的起始地址。
(9)应当使重载运算符的功能类似于该运算符作用于标准类型数据时候时所实现
的功 能。
例如,我们会去重载”+“以实现对象的相加,而不会去重载”+“以实现对象相
减的功能,因为这样不符合我们对”+“原来的认知。
(10)运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非
类 的成员函数也不是友元函数的普通函数
4.10.3 双目运算符重载
//使⽤: L#R
operator#(L,R); //全局函数
L.operator#®; //成员函数
QRGTCVQT
#include
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0) :_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
Complex& operator+=(const Complex &c)
{
this->_x += c._x; this->_y += c._y;
return * this;
}
private:
float _x;
float _y;
};
int main()
{
// int a=10,b=20,c=30;
// a+=b;
// b+=c;
84
// cout<<"a = "<<a<<endl;
// cout<<"b = "<<b<<endl;
// cout<<"c = "<<c<<endl;
// Complex a1(10,0),b1(20,0), c1(30,0);
// 此时的+=重载函数返回 void
// a1 += b1;
// b1 += c1;
// a1.dis();
// b1.dis();
// c1.dis();
//----------------------------------------------------
// int a=10,b=20,c=30;
// a+=b+=c;
// cout<<"a = "<<a<<endl;
// cout<<"b = "<<b<<endl;
// cout<<"c = "<<c<<endl;
// Complex a1(10,0),b1(20,0), c1(30,0);
//此时重载函数+=返回的是 Complex // a1+=b1+=c1;
// a1.dis();
// b1.dis();
// c1.dis();
//----------------------------------------------------
int a = 10, b = 20,c = 30;
(a += b) += c;
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
Complex a1(10,0),b1(20,0), c1(30,0);
// 此时重载函数+=返回的是 Complex &
// ⼀定要注意在连等式中,返回引⽤和返回对象的区别
(a1 += b1) += c1;
a1.dis();
b1.dis();
c1.dis();
return 0;
}
QRGTCVQT
friend Complex& operator-=(Complex &c1, const Complex & c2)
{
}
4.10.4 单目运算符重载
//使⽤: #M 或者 M#
85
operator#(M); //全局函数
M.operator#() //成员函数
QRGTCVQT
┮▁▁
#include
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
friend Complex & operator++(Complex& c);
private:
float _x;
float _y;
};
Complex & operator++(Complex& c)
{
c._x++;
c._y++;
return c;
}
int main()
86
{
int n = 10;
cout<<n<<endl; //10
cout<<++n<<endl; //11
cout<<n<<endl; //11
cout<<++++n<<endl; //13
cout<<n<<endl;
Complex c(10,10);
c.dis(); //10 10
Complex c2=++c;
c2.dis(); //11 11
c.dis(); //11 11
c2 = ++++c;
c2.dis(); //13 13
c.dis(); //13 13
return 0;
}
QRGTCVQT
⛯▁▁
#include
using namespace std;
class Complex
{
public:
Complex(float x=0, float y=0):_x(x),_y(y){}
void dis()
{
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
#if 0
const Complex operator++(int)
{
Complex t = *this; _x++;
_y++;
return t;
}
#endif
friend const Complex operator++(Complex &c,int);
private:
float _x;
float _y;
};
const Complex operator++(Complex &c,int)
{
Complex t(c._x,c._y); c._x++;
c._y++;
return t;
87
}
int main()
{
int n = 10;
cout<<n<<endl; //10
cout<<n++<<endl; //10
cout<<n<<endl; //11
// cout<<n++++<<endl; //13 后++表达式不能连⽤
cout<<n<<endl; //11
Complex c(10);
c.dis();
Complex c2 = c++;
c2.dis();
c.dis();
//c2 = c++++;
//c2.dis();
c.dis();
return 0;
}
4.10.5 输入输出运算符重载
istream & operator>>(istream &,⾃定义类&);
ostream & operator<<(ostream &,⾃定义类&);
通过友元来实现,避免修改 c++的标准库。
QRGTCVQT❭QRGTCVQT
#include
using namespace std;
class Complex {
public:
Complex(float x=0, float y=0)
:_x(x),_y(y){}
void dis() {
cout<<"("<<_x<<","<<_y<<")"<<endl;
}
friend ostream & operator<<(ostream &os, const Complex & c);
friend istream & operator>>(istream &is, Complex &c);
private:
float _x;
float _y;
};
ostream & operator<<(ostream &os, const Complex & c)
88
{
os<<"("<<c._x<<","<<c._y<<")";
return os;
}
istream & operator>>(istream &is, Complex &c)
{
is>>c._x>>c._y;
return is;
}
int main()
{
Complex c(2,3);
cout<<c<<endl;
cin>>c;
cout<<c<<endl;
return 0;
}
4.10.6 友元还是成员
假设,我们有类 Sender 类和 Mail 类,实现发送邮件的功能。
sender<< mail;
sender 左操作数,决定了 operator<<为 Sender 的成员函数,而 mail 决
定了 operator<<要作 Mail 类的友员。
#include
using namespace std;
class Mail;
class Sender
{
public:
Sender(string s):_addr(s){}
Sender& operator<<(const Mail & mail); //成员
private:
string _addr;
};
class Mail
{
public:
89
Mail(string _t,string _c ):_title(_t),_content(_c){}
friend Sender& Sender::operator<<(const Mail & mail);
private:
string _title;
string _content;
};
Sender& Sender::operator<<(const Mail & mail)
{
cout<<“Address:”<<_addr<<endl;
cout<<“Title :”<<mail._title<<endl;
cout<<“Content:”<<mail._content<<endl;
return this;
}
int main()
{
Sender sender(“danbing_at@gmail.com”);
Mail mail(“note”,“meeting at 3:00 pm”);
Mail mail2(“tour”,“One night in beijing”);
sender<<mail<<mail2;
return 0;
}
结论:
1,一个操作符的左右操作数不一定是相同类型的对象,这就涉及到将该操作符函
数定义为谁的友元,谁的成员问题。
2,一个操作符函数,被声明为哪个类的成员,取决于该函数的调用对象(通常是左
操作数)。
3,一个操作符函数,被声明为哪个类的友员,取决于该函数的参数对象(通常是右
操作数)。
90
4.10.7 运算符重载提高
(1) 赋值运算符重载 (operator=)
用一个己有对象,给另外一个己有对象赋值。两个对象均己创建结束后,发
生的赋 值行为。
类名
{
类名& operator=(const 类名& 源对象) 拷⻉体
}
class A
{
A& operator=(const A& another)
{
//函数体
return this;
}
};
规则
1 系统提供默认的赋值运算符重载,一经实现,不复存在。
2 系统提供的也是等位拷贝,也就浅拷贝,一个内存泄漏,重析构。
3 要实再深深的赋值,必须自定义。
4 自定义面临的问题有三个:
1,自赋值
2,内存泄漏
3,重析构。
5 返回引用,且不能用 const 修饰。其目的是实现连等式。
(2) 数组下标运算符 (operator[])
类型 类 :: operator[] ( 类型 ) ;
设 x 是类 X 的一个对象,则表达式
x [ y ]
可被解释为
x . operator [] ( y )
91
92
冤⅁獌䚑⃡ℋ㡑冥作猾屢㼣㧪 个撮扞
(3) 函数调用符号 (operator () )
把类对象像函数名一样使用。
仿函数(functor),就是使一个类的使用看上去象一个函数。其实现就是类
中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
class 类名 {
返值类型 operator()(参数类型) 函数体
}
#include
using namespace std;
class Sqr
{
public:
int operator()(int i)
{
return ii;
}
double operator ()(double d)
{
return dd;
}
};
int main()
{
Sqr sqr;
int i = sqr(4);
double d = sqr(5.5);
cout<<i<<endl;
cout<<d<<endl;
return 0;
}
93
(4) 不可重载&&和||操作符
#include
using namespace std;
class Test
{
public:
Test(int i = 0)
{
this->i = i;
}
Test operator+ (const Test& obj)
{
cout<<“执⾏+号重载函数”<<endl;
Test ret;
ret.i = i + obj.i;
return ret;
}
bool operator&&(const Test& obj)
{
cout<<“执⾏&&重载函数”<<endl;
return i && obj.i;
}
private:
int i;
};
int main()
{
int a1 = 0;
int a2 = 1;
cout<<“注意:&&操作符的结合顺序是从左向右”<<endl;
if( a1 && (a1 + a2) )
{
cout<<“有⼀个是假,则不在执⾏下⼀个表达式的计算”<<endl;
}
Test t1(0);
Test t2(1);
if ( t1 && (t1 + t2) )
{
//t1 && t1.operator(t2)
// t1.operator&&( t1.operator+(t2) )
cout<<“两个函数都被执⾏了,⽽且是先执⾏了+”<<endl;
}
94
return 0;
}
C++如果重载&&或者|| 将无法实现短路规则
冤⅁䚑⃡ℋ⸸万ℓ作
㩥持ⓞ㡑屢㼣
/[5VTKPIC
/[5VTKPIC
ňFFFFʼn
/[5VTKPIDC
ㄙ䠉䥥㞮∽万
=?
(7)解引用与智能指针
常规意义上讲,new 或是 malloc 出来的堆上的空间,都需要手动 delete 和
free 的。但在其它高级语言中,只需申请无需释放的功能是存在的。
c++中也提供了这样的机制。我们先来探究一下实现原理。
ㄙ岥ㅵ䠉
void foo()
{
Ap = new A;
// do something
delete p;
}
㥛叞㗨杩
#include
#include
using namespace std;
class A
95
{
public:
A() {
cout<<“A constructor”<<endl;
}
~A() {
cout<<“A destructor”<<endl;
}
void dis() {
cout <<"class A’s dis() " <<endl;
}
};
int main()
{
//使⽤智能指针 auto_ptr
auto_ptr p (new A);
p->dis();
return 0;
}
哋K㥛叞㗨杩
#include
#include
using namespace std;
class A
{
public:
A()
{
cout<<“A constructor”<<endl;
}
~A()
{
cout<<“A destructor”<<endl;
}
void dis()
{
cout<<“in class A’s dis”<<endl;
}
};
class PMA
{
public:
PMA(A p) :_p§{}
96
~PMA()
{
delete _p;
}
A& operator()
{
return _p;
}
A operator->()
{
return _p;
}
private:
A * _p;
};
->❭ 撮扞㬝ㇰ
类名& operator*() {
函数体
}
类名* operator->() {
函数体
}
97
暀荶
∽设计6&CVG类
定义一个处理日期的类 TDate,它有 3 个私有数据成员:Month,Day,Year 和若干个 公
有成员函数,并实现如下要求:
1构造函数重载
2成员函数设置缺省参数
3可使用不同的构造函数来创建不同的对象
4定义一个友元函数来打印日期
作业2 废庂⃡ℋ䫊椖作
设计一个 33 的矩阵类 class Matrix,通过一数组进行初始化。
要求如下:
1默认构造(初始化为 0),有参构造(数组作实参)
2重载+ / +=
3重载 / *=
4实现输出
98
class Matrix
{
public:
Matrix(void);
Matrix(int p[][3]);
private:
int data[3][3];
}