类class
class 类名
{
public:
//公共成员,谁都可以访问
protected:
//保护成员,只有自己和子类可以访问
private:
//私有成员,只有自己可以访问
};
注意:class不提供访问控制限定符,默认访问控制限定符为private:(私有)
struct不提供访问控制限定符,默认访问控制限定符为public:(公有)
访问控制限定符作用范围是碰到下一个访问控制限定符
#include <iostream>
#include <cstring>
using namespace std;
class Human
{
public:
void setinfo(const char* name=" ",int age=0)
{
strcpy(m_name,name);
m_age=age;
}
void getinfo()
{
cout<<"姓名:"<<m_name<<",年龄:"<<m_age<<endl;
}
private:
int m_age;
char m_name[256];
};
int main(void)
{
Human h;//定义h(给h分配内存空间)
//在h所占内存空间中 定义m_age,初始值为随机数
//在h所占的内存空间中 定义m_name ,初始值为随机数
cout <<"h的大小:"<<sizeof(h)<<endl;//260
h.setinfo("zgj",22);
h.getinfo();
return 0;
}
成员函数参数--this
#include <iostream>
#include <cstring>
using namespace std;
/*当前程序有两个对象h/h2*/
class Human
{
public:
/*换名:W _ZN5Human7setinfoEPKci,系统和编译器不同,换名有区别*/
void setinfo(/*Human* this*/const char* name=" ",int age=0)/*存在代码段且只存一份*/
{//成员函数是那个类的,就补哪个类的指针
strcpy(m_name,name);
m_age=age;
}
/*换名:W _ZN5Human7getinfoEv,系统和编译器不同,换名有区别*/
void getinfo(/*Human* this*/)/*存在代码段,且只存一份*/
{
cout<<"姓名:"<<m_name<<",年龄:"<<m_age<<endl;
}
private:
int m_age;
char m_name[256];
};
int main(void)
{
Human h;//定义h(给h分配内存空间)
//在h所占内存空间中 定义m_age,初始值为随机数
//在h所占的内存空间中 定义m_name,初始值为随机数
cout <<"h的大小:"<<sizeof(h)<<endl;//260
h.setinfo("zgj",22);//W _ZN5Human7setinfoEPKci(&h,"zgj",22)
h.getinfo();//W _ZN5Human7getinfoEv(&h)
Human h2;//定义h2(给h分配内存空间)
//在h2所占内存空间中 定义m_age,初始值为随机数
//在h2所占的内存空间中 定义m_name,初始值为随机数
cout <<"h2的大小:"<<sizeof(h2)<<endl;//260
h2.setinfo("zgj1",22);//W _ZN5Human7setinfoEPKci(&h2,"zgj1",22)
h2.getinfo();//W _ZN5Human7getinfoEv(&h2)
return 0;
哪个对象 调用 成员函数,则成员函数的this参数就指向哪个对象
同一个类的不同对象各自拥有一份独立的成员变量。
同一个类的不同对象彼此共享同一份成员函数。
类的每个成员函数(除静态成员函数外),都有一个隐藏的指针型参数,形参名为 this,指向调用该成员函数的对象,这就是this指针.
在类的成员函数中(除静态成员函数外),对所有成员的访问,都是通过this指针进行的
大多数情况下不需要显式使用this指针,但当类的成员函数与该类成员函数的参数取了相同标识符,这时在成员函数内部可以使用this指针区分,返回基于this指针的自引用,以支持串连调用,将this指针作为函数的参数,以实现对象交互
#include <iostream>
using namespace std;
class Integer
{
public:
void setinfo(/*Integer* this*/int i)
{
this->i = i;//(1)必须自己写this的情况
}
void getinfo(/*Integer* this*/)
{
cout<<this->i<<endl;
}
Integer& increment(/*Integer* this*/)
{
++/*this->*/i;
return *this;//返回基于this指针的自引用
}
private:
int i;
};
int main(void)
{
Integer ix;
ix.setinfo(10);
ix.getinfo();
ix.increment().increment();//串联调用
ix.getinfo();
return 0;
}
常对象与常函数
被const关键字修饰的对象、对象指针或对象引用,统称为常对象
const User user;
const User* cptr = &user;
const User& cref = user;
在类成员函数的形参表之后,函数体之前加上const关键字,该成员函数的this指针即具有常属性,这样的成员函数被称为常函数
返回类型 函数名 ( 形参表 ) const
函数体;
}
};
#include <iostream>
using namespace std;
class Integer
{
public:
void setinfo(/*Integer* this*/int i)//非 常函数
{
m_i = i;//(1)必须自己写this的情况
}
void getinfo(/*Integer* this*/)//非 常函数
{
cout<<"非常:"<<m_i<<endl;
}
void getinfo(/*const Integer* this*/) const//常函数
{
m_i=12;//当m_i有mutable关键字修饰时可这样修改
/*const_cast<Integer*>(this)->m_i=12;*///没有m_i没有mutable关键字修饰时可这样修改
cout<<"常"<<m_i<<endl;
}
private:
mutable int m_i;
};
int main(void)
{
Integer ix;//非 常对象
ix.setinfo(10);
ix.getinfo();//getinfo(&ix)-->实参类型为Integer
const Integer cix=ix;//常对象
cix.getinfo();
return 0;
}
构造函数
构造函数特性
- 函数名与类名相同。
- 无返回值。
- 编译器自动调用对应的构造函数。
- 构造函数可以重载。
构造函数调用时间
- 在定义对象同时自动被调用,且仅被调用一次
- 在什么情况下会 定义对象 :(1)对象定义语句,(2)new操作符
构造函数的作用
–定义对象的各个成员变量并赋初值。设置对象的初始状态
–在对象定义之初想实现的任何操作
代码
#include <iostream>
#include <cstring>
using namespace std;
class Human
{
public:
Human(/*Human* this*/int age=0,const char* name="无名");//构造函数
void getinfo(/*Human* this*/);//声明
private:
int m_age;
char m_name[256];
};
void Human:: getinfo()//定义
{
cout<<"姓名:"<<m_name<<",年龄:"<<m_age<<endl;
}
//注意:函数的默认值只能在声明中指定,不能在定义中指定
Human::Human(/*Human* this*/int age,const char* name)//构造函数
{
//在this所指向的内存空间中 定义m_age,初值为随机数
//在this所指向的内存空间中 定义m_name,初值为随机数
cout<<"Human类的构造函数被调用"<<endl;
strcpy(m_name,name);
m_age=age;
}
int main(void)
{
Human h(22,"zgj");//定义h,利用h.Human()
h.getinfo();
return 0;
}
类的定义与实例化
类名 对象 ; // 注意不要加空括号
类 名 对象 ( 实参表 );
类名 对象 数组 [ 元素个数 ];
类 名 对象 数组 [ 元素个数 ] = { 类名 ( 实参表 ), ...};
类 名 对象 数组 [] = { 类名 ( 实参表 ), ...};
类 名* 对象指针 = new 类名 ();
类 名* 对象指针 = new 类名 ( 实参表 );
delete 对象指针 ;
类名* 对象数组指针 = new 类名 [ 元素个数 ];
类 名* 对象数组指针 = new 类名 [ 元素个数 ] { 类名 ( 实参表 ), ...};
// 上面的写法需要 编译器支持 C ++11 标准
delete[] 对象数组指针 ;
//定义对象的方法
#include <iostream>
#include <cstring>
using namespace std;
class Human
{
public:
Human(/*Human* this*/int age=0,const char* name="无名");//构造函数
void getinfo(/*Human* this*/);//声明
private:
int m_age;
char m_name[256];
};
void Human:: getinfo()//定义
{
cout<<"姓名:"<<m_name<<",年龄:"<<m_age<<endl;
}
//注意:函数的默认值只能在声明中指定,不能在定义中指定
Human::Human(/*Human* this*/int age,const char* name)//构造函数
{
//在this所指向的内存空间中 定义m_age,初值为随机数
//在this所指向的内存空间中 定义m_name,初值为随机数
cout<<"Human类的构造函数被调用"<<endl;
strcpy(m_name,name);
m_age=age;
}
int main(void)
{
Human h(22,"zgj");//定义h,利用h.Human()
h.getinfo();
Human h2;//定义h2,利用h2.Human()
h2.getinfo();
Human h3[3];//定义3个Human类对象,分别利用这三个Human类对象.Human()
for(int i=0;i<3;i++)
{
h3[i].getinfo();
}
Human h4[3]={ Human(22,"z"), Human(20,"g"), Human(25,"j") };
for(int i=0;i<3;i++)
{
h4[i].getinfo();
}
Human h5[]={ Human(22,"z"), Human(20,"g"), Human(25,"j"), Human(23,"w") };
for(int i=0;i<sizeof(h5)/sizeof(h5[0]);i++)
{
h5[i].getinfo();
}
Human* ph=new Human;//定义Human类堆对象,利用Human类堆对象.Human()
(*ph).getinfo();
delete ph;
ph=NULL;
Human* ph2=new Human();
(*ph2).getinfo();
delete ph2;
ph2=NULL;
Human* ph3=new Human(22,"w");
(*ph3).getinfo();
ph3=NULL;
Human* ph4=new Human[3];//定义3个Human类堆对象,分别利用这3个堆对象.Human()
for(int i=0;i<3;i++)
{
ph4[i].getinfo();
}
delete[] ph4;
ph4=NULL;
Human* ph5=new Human[3]{Human(18,"w"),Human(20,"r"),Human(21,"I")};
for(int i=0;i<3;i++)
{
ph5[i].getinfo();
}
delete[] ph5;
ph5=NULL;
return 0;
}
83,1 底端
string
//string类几种使用方法
#include <iostream>
#include <cstring>
using namespace std;
int main(void)
{
string s1("hello");//定义s1,利用s1.string("hello")--->
cout << "s1:" << s1/*.c_str()*/ << endl;
//如果在做初始化,并且 "=" 两边类型完全一致,那么=xxx和(xxx)无差别
string s2(s1);//=s1;定义s2,利用s2.string(s1)-->s2维护的内容和s1维护的内容相同
cout<<"s2:"<<s2<<endl;
string s3;//定义s3,利用s3.string()-->s3维护的内容为"\0"
cout<<"s3:"<<s3<<endl;
s3=s2;//s3.operator=(s2)-->s3维护的内容和s2维护的内容相同
cout<<"s3:"<<s3<<endl;
//无论初始化还是赋值,只要等号两边类型不一致,将触发类型转换操作
string s4="hello";//定义匿名string类对象,并利用匿名string类对象.string("hello")-->匿名对象维护"hello"
//string s4=匿名string类对象-->s4维护的内容和匿名对象维护的内容相同
//-->s4维护的内容为"hello"
cout<<"s4:"<<s4<<endl;
string s5;//定义s5,利用s5.string()-->s5维护的内容为"\0"
s5="hello";//定义匿名string类对象,并利用匿名string类对象.string("hello")-->>匿名对象维护"hello"
//s5=匿名string类对象-->s5维护的内容和匿名对象维护的内容相同
//-->s5维护的内容为"hello"
cout<<"s5:"<<s5<<endl;
return 0;
}
构造函数的重载
构造函数可以通过参数表的差别化形成重载
重载的构造函数,通过构造函数的实参类型进行匹配
不同的构造函数,表示对象的不同创建方式
使用缺省参数(默认参数)可以减少构造函数重载的数量
//构造函数的重载
#include <iostream>
using namespace std;
class Human
{
public:
#if 0
Human()
{
m_age=0;
m_name="无名";
}
Human(int age)
{
m_age=age;
m_name="无名";
}
#endif
Human(int age=0,const char* name="无名")
{
m_age=age;
m_name=name;
}
void getinfo(/*Human* this*/)
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
private:
int m_age;
string m_name;
};
int main(void)
{
Human h;//定义h,利用h.Human()
h.getinfo();
Human h2(22);//定义h2,利用h2.Human(22)
h2.getinfo();
Human h3(22,"z");//定义h3,利用h3.Human(22,"z")
h3.getinfo();
return 0;
}
构造函数分类
无参构造函数
亦称缺省构造函数,但其未必真的没有任何参数,为一个有参构造函数的每个参数都提供一个缺省值,同样可以达到无参构造函数的效果
问题:有参构造每个参数有默认值与每个参数没有默认值的区别
回答:当构造函数的形参没有默认值时,调用该构造函数时必须显式地提供实参来初始化对象,如果没有提供实参,将会导致编译错误。当构造函数的形参具有默认值,可以选择省略实参,这种情况下,如果省略实参,将使用默认值来初始化对象。
如果一个类没有定义任何构造函数,那么编译器会为其提供一个无参构造函数
对基本类型的成员变量进行定义,并初始化为随机数
对类类型的成员变量进行定义,调用相应类型的无参构造函数
如果一个类定义了构造函数,无论这个构造函数是否带有参数,编译器都不会再为这个类再提供无参构造函数
有时必须为一个类提供无参构造,仅仅因为它可能作为另一个类的类类型成员变量
#include <iostream>
using namespace std;
class A
{
public:
/* A(int i)//此时A类中没有无参构造*/
A(int i=0)//当前A类中 有无参构造函数
{
m_i=i;
}
private:
int m_i;
};
class Human
{
public:
#if 0
Human()
{
//在this指针指向的内存空间中 定义m_age,初值为随机数
//在this指针指向的内存空间中 定义m_name,利用m_name.string()
}
#endif
Human(int age=0,const char* name="无名")
{
//在this指针指向的内存空间中 定义m_age,初值为随机数
//在this指针指向的内存空间中 定义m_name,利用m_name.string()
//在this指针指向的内存空间中 定义m_a,利用m_a.A()
cout<<"Human类的缺省构造函数被调用"<<endl;
m_age=age;
m_name=name;
}
void getinfo(/*Human* this*/)
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
private:
int m_age;//基本类型的成员变量
string m_name;//类类型的成员变量
A m_a;//类类型的成员变量
};
int main(void)
{
Human h;//定义h,利用h.Human()
h.getinfo();
Human h2(22,"z");
h2.getinfo();
return 0;
}
拷贝构造函数
形如
class 类名
{
类名 (const 类名& that) { … }
};
的构造函数被称为拷贝构造函数。(构造函数必须单参,唯一的一个参数必须与类名相同)
作用:利用一个已定义的对象,来定义其同类型的副本对象,即对象克隆
使用:1.如果一个类没有定义拷贝构造函数,那么编译器会为其提供一个默认拷贝构造函数
(1)对基本类型成员变量进行定义,并赋初值(按字节复制)
(2)对类类型成员变量进行定义,并调用相应类型的拷贝构造函数
2. 如果自己定义了拷贝构造函数,编译器将不再提供默认拷贝构造函数,这时所有与成员
复制有关的操作,都必须在自定义拷贝构造函数中自己编写代码完成
3. 若默认拷贝构造函数不能满足要求,则需自己定义
#include <iostream>
using namespace std;
class Human
{
public:
#if 0
Human()
{
//在this指针指向的内存空间中 定义m_age,初值为随机数
//在this指针指向的内存空间中 定义m_name,利用m_name.string()
}
#endif
Human(int age=0,const char* name="无名")
{
//在this指针指向的内存空间中 定义m_age,初值为随机数
//在this指针指向的内存空间中 定义m_name,利用m_name.string()
cout<<"Human类的缺省构造函数被调用"<<endl;
m_age=age;
m_name=name;
}
#if 0
//如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数
Human(const Human& that)//编译器提供的拷贝构造
{
//在this指针指向的内存空间中 定义m_age,指定初始值为m_age=that.m_age
//在this指针指向的内存空间中 定义m_name,利用m_name.string(that.m_name)
}
#endif
Human(const Human& that)//自定义的拷贝构造
{
//在this指针指向的内存空间中 定义m_age,没有初始值
//在this指针指向的内存空间中 定义m_name,也没有初值
cout<<"Human类拷贝构造函数被调用"<<endl;
m_age=that.m_age;
m_name=that.m_name;
}
void getinfo(/*Human* this*/)
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
private:
int m_age;//基本类型的成员变量
string m_name;//类类型的成员变量
};
int main(void)
{
Human h;//定义h,利用h.Human()
h.getinfo();
Human h2(22,"z");
h2.getinfo();
Human h3=h2;//定义h3,利用h3.Human(h2)-->拷贝构造函数
h3.getinfo();
return 0;
}
拷贝构造函数的调用时机
(1)用已定义对象作为同类型对象的构造实参
(2)以对象的形式向函数传递参数
(3)从函数中返回对象
注意:某些拷贝构造过程会因编译优化而被省略
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名")
{
//在this指针指向的内存空间中 定义m_age,初值为随机数
//在this指针指向的内存空间中 定义m_name,利用m_name.string()
cout<<"Human类的缺省构造函数被调用"<<endl;
m_age=age;
m_name=name;
}
Human(const Human& that)//自定义的拷贝构造
{
//在this指针指向的内存空间中 定义m_age,没有初始值
//在this指针指向的内存空间中 定义m_name,也没有初值
cout<<"Human类拷贝构造函数被调用"<<endl;
m_age=that.m_age;
m_name=that.m_name;
}
void getinfo(/*Human* this*/)
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
private:
int m_age;//基本类型的成员变量
string m_name;//类类型的成员变量
};
void foo(Human v)
{
}
Human bar()
{
Human m;
return m;
}
int main(void)
{
Human h2(22,"z");
Human h3=h2;//定义h3,利用h3.Human(h2)-->拷贝构造函数
foo(h3);//触发拷贝构造函数
Human h4=bar();//触发两次拷贝构造,但会被g++编译器优化掉
//可以加-fno-elide-constructors选项取消优化
return 0;
}
执行结果(不让编译器优化):
自定义构造函数和编译器定义构造函数
拷贝赋值函数
形如
class 类名
{
类名& operator= (const 类名& that) { … }
};//(函数名必须为operator= 参数只能有一个且唯一的参数的类型必须与类名相同)
的函数被称为拷贝赋值函数,用于一个已定义的对象给同类型的对象赋值,即对象赋值
如果一个类没有定义拷贝赋值函数,那么编译器会为其提供一个默认拷贝赋值函数
(1)对基本类型成员变量,值传递(按字节复制)
(2)对类类型成员变量,调用相应类型的拷贝赋值函数
如果自己定义了拷贝赋值函数,编译器将不再提供默认拷贝赋值函数,这时所有与成员复制有关的
操作都必须在自定义拷贝赋值函数中自己编写代码完成。
若默认拷贝赋值函数不能满足要求时,则需自己定义
#include <iostream>
using namespace std;
class Human
{
public:
#if 0
Human()
{
//在this指针指向的内存空间中 定义m_age,初值为随机数
//在this指针指向的内存空间中 定义m_name,利用m_name.string()
}
#endif
Human(int age=0,const char* name="无名")
{
//在this指针指向的内存空间中 定义m_age,初值为随机数
//在this指针指向的内存空间中 定义m_name,利用m_name.string()
cout<<"Human类的缺省构造函数被调用"<<endl;
m_age=age;
m_name=name;
}
#if 0
//如果类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数
Human(const Human& that)//编译器提供的拷贝构造
{
//在this指针指向的内存空间中 定义m_age,指定初始值为m_age=that.m_age
//在this指针指向的内存空间中 定义m_name,利用m_name.string(that.m_name)
}
#endif
Human(const Human& that)//自定义的拷贝构造
{
//在this指针指向的内存空间中 定义m_age,没有初始值
//在this指针指向的内存空间中 定义m_name,也没有初值
cout<<"Human类拷贝构造函数被调用"<<endl;
m_age=that.m_age;
m_name=that.m_name;
}
#if 0
//如果类中没有拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数
Human& operator=(const Human& that)//编译器提供的默认拷贝赋值函数
{
this->m_age=that.m_age;
this->m_name=that.m_name;//this->m_name.operator=(that.m_name)
return *this;//返回this指针的自引用,谁调拷贝赋值函数就返回谁本身
}
#endif
Human&/*void*/ operator=(const Human& that)//void返回值 不能串联调用
{
// 编译器不会在拷贝赋值函数中塞任何操作
cout<<"Human类的拷贝赋值函数被调用"<<endl;
this->m_age=that.m_age;
this->m_name=that.m_name;//this->m_name.operator=(that.m_name)
return *this;
}
void getinfo(/*Human* this*/)
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
private:
int m_age;//基本类型的成员变量
string m_name;//类类型的成员变量
};
int main(void)
{
Human h;//定义h,利用h.Human()
h.getinfo();
Human h2(22,"z");
h2.getinfo();
Human h3=h2;//定义h3,利用h3.Human(h2)-->拷贝构造函数
h3.getinfo();
Human h4;
cout<<"h4被赋值前:";
h4.getinfo();
h4=h3;//h4.operator=(h3)-->触发拷贝赋值函数
cout<<"h4被赋值后:";
h4.getinfo();
return 0;
}
初始化表
1.通过在类的构造函数中使用初始化表,可以通知编译器该类的成员变量如何被初始化
2.类中的基本类型成员变量,最好在初始化表中显式指明如何初始化,否则初值不确定。
3.类中的类类型成员变量,也最好在初始化表中显式指明如何初始化,否则将调动相应类型的无参构造函数。
4.类的常量型和引用型成员变量,必须在初始化表中显式初始化。
5.类的成员变量按其在类中的声明顺序依次被初始化,而与其在初始化表中的顺序无关。
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名",float score=0.0):m_age(age),m_name(name),m_score(score)
{
//在this指针指向的内存空间中 定义m_age,初值为age
//在this指针指向的内存空间中 定义m_name,利用m_name.string(name)
//在this指针指向的内存空间中 定义m_score,初值为score
cout<<"Human类的缺省构造函数被调用"<<endl;
}
Human(const Human& that):m_age(that.m_age),m_name(that.m_name),m_score(that.m_score)
{
//在this指针指向的内存空间中 定义m_age,初值为that.m_age
//在this指针指向的内存空间中 定义m_name,利用m_name.string(that.m_name)
//在this指针指向的内存空间中 定义m_score,初值为score
cout<<"Human类拷贝构造函数被调用"<<endl;
}
void getinfo(/*Human* this*/)
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<",成绩:"<<this->m_score<<endl;
}
private:
int m_age;
string m_name;
const float m_score;//常量型的成员变量
};
int main(void)
{
Human h2(22,"z",88.8);
h2.getinfo();
Human h3=h2;
h3.getinfo();
return 0;
}
类型转换构造函数
形如:
class 目标类型
{
目标类型 (const 源类型& src) { … }
};//(只有一个参数,且仅有的一个参数与类名相同)
的构造函数被称为类型转换构造函数。
用于:(1)利用一个已定义的对象, 来定义另一个不同类型的对象,
(2)实现从源类型到目标类型的隐式类型转换的目的
通过explicit关键字,可以强制这种通过类型转换构造函数实现的类型转换必须通过静态转换显式地进行.
class 目标类型
{
explicit 目标类型 (const 源类型& src) { … }
};
#include <iostream>
using namespace std;
class Cat
{
public:
Cat(const char* name):m_name(name)
{
}
void talk()
{
cout<<m_name<<":喵喵~~"<<endl;
}
private:
string m_name;
friend class Dog;
};
class Dog
{
public:
Dog(const char* name):m_name(name)
{
}
explicit Dog(const Cat& that):m_name(that.m_name)//类型转换构造函数
{
//在this指向的内存空间中,定义m_name,初值为that.m_name
cout<<"Dog类的类型转换构造函数被调用"<<endl;
}
void talk()
{
cout<<m_name<<":汪汪~~"<<endl;
}
private:
string m_name;
};
int main(void)
{
Cat smallwhite("小白");
smallwhite.talk();
// Dog bigyellow(smallwhite);//定义bigyellow,利用bigyellow.Dog(smallwhite)
// Dog bigyellow=smallwhite;//定义匿名Dog类对象,利用匿名Dog类对象.Dog(smallwhite)
Dog bigyellow=static_cast<Dog>(smallwhite);
bigyellow.talk();
return 0;
}
析构函数
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名"):m_age(age),m_name(name)
{
//在this指针指向的内存空间中 定义m_age,初值为age
//在this指针指向的内存空间中 定义m_name,利用m_name.string(name)
cout<<"Human类的缺省构造函数被调用"<<endl;
}
Human(const Human& that):m_age(that.m_age),m_name(that.m_name)
{
//在this指针指向的内存空间中 定义m_age,初值为that.m_age
//在this指针指向的内存空间中 定义m_name,利用m_name.string(that.m_name)
cout<<"Human类拷贝构造函数被调用"<<endl;
}
Human&/*void*/ operator=(const Human& that)//不能使用初始化表
{
// 编译器不会在拷贝赋值函数中塞任何操作
cout<<"Human类的拷贝赋值函数被调用"<<endl;
this->m_age=that.m_age;
this->m_name=that.m_name;//this->m_name.operator=(that.m_name)
return *this;
}
#if 0
//如果类没有提供析构函数,编译器将提供一个默认的析构函数
~Human()
{
//对于基本类型的m_age,什么都不做
//对于类类型的m_name,利用m_name.~string()
//释放m_age/m_name本身所占内存空间
}
#endif
~Human()
{
cout<<"Human类的析构函数被调用"<<endl;
//对于基本类型的m_age,什么都不做
//对于类类型的m_name,利用m_name.~string()
//释放m_age/m_name本身所占内存空间
}
void getinfo(/*Human* this*/)
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
private:
int m_age;//基本类型的成员变量
string m_name;//类类型的成员变量
};
int main(void)
{
Human h;//定义h,利用h.Human()
h.getinfo();
Human h2(22,"z");
h2.getinfo();
Human h3=h2;//定义h3,利用h3.Human(h2)-->拷贝构造函数
h3.getinfo();
Human h4;
cout<<"h4被赋值前:";
h4.getinfo();
h4=h3;//h4.operator=(h3)-->触发拷贝赋值函数
cout<<"h4被赋值后:";
h4.getinfo();
return 0;
}
•通常情况下,若对象在其生命周期的最终时刻,并不持有任何动态分配的资源,可以不定义析构函数
•但若对象在其生命周期的最终时刻,持有动态资源则必须自己定义析构函数,释放对象所持有的动态资源
•析构函数的功能并不局限在释放资源上,它可以执行我们希望在对象被释放之前执行的任何操作
深浅 拷贝构造 与 拷贝赋值
•如果类不提供拷贝构造和拷贝赋值编译器将提供默认的拷贝构造和拷贝赋值,而默认的拷贝构造和拷贝赋值函数,对于指针型成员变量都是只复制地址,而并不是复制地址指向的数据,这将导致浅拷贝问题
•为了获得完整意义上的对象副本,必须自己定义拷贝构造和拷贝赋值,针对指针型成员变量做深拷贝
•相对于拷贝构造,拷贝赋值需要做更多的工作
•无论是拷贝构造还是拷贝赋值,其默认实现对任何类型的指针成员都是简单地复制地址,因此应尽量避免使用指针型成员变量
•出于具体原因的考虑,确实无法实现完整意义上的拷贝构造和拷贝赋值,可将它们私有化,以防误用
•如果为一个类提供了自定义的拷贝构造函数,就没有理由不提供相同逻辑的拷贝赋值运算符函数
#include <iostream>
#include <cstring>
using namespace std;
class String
{
public:
String(const char* psz=" "):m_psz(new char[strlen(psz)+1])
{
//在this指向的内存空间中,定义m_psz,初值为指向一块堆内存(动态资源)
strcpy(m_psz,psz);
}
#if 0
String(const String& that)//编译器提供的拷贝构造
{
//在this指向得内存空间中 定义char* m_psz,初始化为char* m_psz=that.m_psz。只复制了地址,没有复制地址所指向的数据
}
#endif
~String()
{
delete[] this->m_psz;
this->m_psz=NULL;
}
String (const String& that):m_psz(new char[strlen(that.m_psz)+1])//深拷贝构造
{
strcpy(m_psz,that.m_psz);//复制数据,不复制地址
}
#if 0
String& operator=(const String& that)//编译器提供的默认的拷贝赋值函数
{
m_psz=that.m_psz;
return *this;
}
#endif
String& operator=(const String& that)
{
if(this!=&that)//防止自赋值
{
delete[] this->m_psz;//释放旧资源
this->m_psz=new char[strlen(that.m_psz)+1];
strcpy(this->m_psz,that.m_psz);
return *this;//返回自引用
}
}
char* c_str() {return m_psz;}
private:
char* m_psz;
};
int main(void)
{
String s1("hello");
cout<<"s1:"<<s1.c_str()<<",&s1:"<<(void*)s1.c_str()<<endl;
String s2(s1);//定义s2,利用s2.String(s1)-->触发拷贝构造
cout<<"s2:"<<s2.c_str()<<",&s2:"<<(void*)s2.c_str()<<endl;
String s3;
s3=s2;//触发拷贝赋值函数
cout<<"s3:"<<s3.c_str()<<",&s3:"<<(void*)s3.c_str()<<endl;
return 0;
}
类的静态成员
静态成员变量
•静态成员变量 属于类 而不属于对象
–静态成员变量不包含在对象中,进程级生命期
–静态成员变量的定义和初始化,只能在类的外部而不能在构造函数中进行。
–静态成员变量依然受类作用域和访问控制限定符的约束
–访问静态成员变量,既可以通过类也可以通过对象
–静态成员变量为该类的所有对象实例所共享
#include <iostream>
using namespace std;
//非静态(普通)成员变量:属于对象,对象的生命期
//静态成员变量:不属于对象,生命期为进程级
class A
{
public:
A()
{
}
int m_i;//声明
/*private:*///加上私有成员限定符,类外可以不能访问
static int m_si;//生命
};
int A::m_si=0;//全局域中定义-->进程级生命期
int main(void)
{
A a,b;//静态成员变量属于类,不属于对象
cout<<"a的大小:"<<sizeof(a)<<endl;//4
cout<<"b的大小:"<<sizeof(b)<<endl;//4
A::m_si=100;
cout<<A::m_si<<endl;
a.m_si=200;//会被编译器修改为:A::m_si=200;
cout<<b.m_si/*A::m_si*/<<endl;//静态成员变量 被 A类的所有对象共享
return 0;
}
静态成员函数
#include <iostream>
using namespace std;
//非静态(普通)成员变量:属于对象,对象的生命期
//静态成员变量:不属于对象,生命期为进程级
class A
{
public:
void foo(/*A* this*/)
{
cout<<"A::foo()"<<endl;
/*非静态成员函数即可访问静态成员也可访问非静态成员*/
cout<<m_si<<endl;
cout<<m_i<<endl;
bar();
}
static void bar1()
{
}
static void bar()
{
cout<<"A::bar()"<<endl;
cout<<m_si<<endl;//OK
bar1();//OK
/*
cout<<m_i<<endl;//error
foo();//error
静态成员函数内部只能访问静态成员,不能访问非静态成员
*/
}
int m_i;//普通成员变量
static int m_si;//静态成员函数
};
int main(void)
{
A a,b;
a.foo();//foo(&a)
b.foo();//foo(&b)
A::bar();//属于类
a.bar();//被改为A::bar()
b.bar();//被改为A::bar()
return 0;
}
静态成员
•事实上,类的静态成员变量和静态成员函数,更象是普通的全局变量和全局函数,只是多了一层类作用域和访问控制限定符的约束,相当于具有成员访问属性的全局变量和全局函数
单例模式
•一个类仅有一个实例(对象)
•将包括,类的拷贝构造函数在内的所有构造函数私有化 , 防止使用者在类的外部创建对象
•公有静态成员函数getInstance()是获取对象实例的唯一渠道
饿汉式:
无论用不用,程序启动即创建
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton& getInstance()
{
return s_instance;
}
private:
Singleton() {}
Singleton(const Singleton& that) {}
static Singleton s_instance;//唯一对象
};
Singleton Singleton::s_instance;
int main()
{
Singleton& s1=Singleton::getInstance();
Singleton& s2=Singleton::getInstance();
Singleton& s3=Singleton::getInstance();
cout<<"&s1:"<<&s1<<" ,&s2:"<<&s2<<" ,&s3:"<<&s3<<endl;
return 0;
}
懒汉式:
用的时候创建,不用了即销毁
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton& getInstance()
{
if(s_instance==NULL)
{
s_instance=new Singleton;//唯一对象
cout<<"创建了唯一对象"<<endl;
}
++s_counter;
return *s_instance;
}
void releaseIntance()
{
--s_counter;
if(s_counter==0)
{
delete s_instance;
s_instance=NULL;
cout<<"销毁了唯一对象"<<endl;
}
}
private:
Singleton() {}
Singleton(const Singleton& that) {}
static Singleton* s_instance;//不是唯一对象
static int s_counter;
};
Singleton* Singleton::s_instance=NULL;
int Singleton::s_counter=0;
int main()
{
Singleton& s1=Singleton::getInstance();//第一次调用创建唯一对象
Singleton& s2=Singleton::getInstance();//返回第一次创建的唯一地址
Singleton& s3=Singleton::getInstance();
cout<<"&s1:"<<&s1<<" ,&s2:"<<&s2<<" ,&s3:"<<&s3<<endl;
s1.releaseIntance();
s2.releaseIntance();
s3.releaseIntance();
return 0;
}
操作符重载
操作符标记与操作符函数
•双目操作符表达式:L#R
–成员函数形式:L.operator# (R)
左操作数是调用对象,右操作数是参数对象
–全局函数形式:operator# (L, R)
左操作数是第一参数,右操作数是第二参数
•单目操作符表达式:#O/O#
–成员函数形式:O.operator# ()
–全局函数形式:operator# (O)
•三目操作符表达式:F#S#T
–无法重载
•运算类双目操作符:+、-、*、/等
–左右操作数均可以为非常左值、常左值或右值
–表达式的结果为右值
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名"):m_age(age),m_name(name)
{
}
void getinfo()
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
#if 0
Human operator+(/*const Human* this*/ const Human& r) const
{
return Human(m_age+r.m_age,(m_name+"+"+r.m_name).c_str());
}
#endif
private:
int m_age;
string m_name;
friend Human operator+(const Human& l,const Human& r);
};
//全局形式的操作符函数
Human operator+(const Human& l,const Human& r)
{
return Human(l.m_age+r.m_age,(l.m_name+"+"+r.m_name).c_str());
}
int main(void)
{
Human a(22,"z"),b(23,"g");//非常左值
const Human c(24,"j"),d(25,"w");//常左值
Human res=a+b;//a.operator+(b) 或 operator+(a,b)
res.getinfo();
res=c+d;
res.getinfo();
res=Human(20,"w")+Human(21,"r");
res.getinfo();
return 0;
}
友元
•可以通过friend关键字,把一个全局函数、另一个类的成员函数或者另一个类整体,声明为授权类的友元
•友元拥有访问授权类任何非公有成员的特权
•友元声明可以出现在授权类的公有、私有或者保护等任何区域,且不受访问控制限定符的约束
•友元不是成员,其作用域并不隶属于授权类,也不拥有授权类类型的this指针。
双目操作符的重载
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名"):m_age(age),m_name(name)
{
}
void getinfo()
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
#if 0
Human operator+(/*const Human* this*/ const Human& r) const
{
return Human(m_age+r.m_age,(m_name+"+"+r.m_name).c_str());
}
#endif
Human& operator+=(const Human& r)
{
this->m_age=this->m_age+r.m_age;
this->m_name=this->m_name+"+"+r.m_name;
return *this;
}
private:
int m_age;
string m_name;
};
int main(void)
{
Human a(22,"z"),b(23,"g");//非常左值
const Human c(24,"j"),d(25,"w");//常左值
a+=b;
a.getinfo();
((a+=b)+=c)+=Human(20,"r");
a.getinfo();
return 0;
}
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名"):m_age(age),m_name(name)
{
}
void getinfo()
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
bool operator==(const Human& r) const
{
return this->m_age==r.m_age&&this->m_name==r.m_name;
}
bool operator!=(const Human& r) const
{
return this->m_age!=r.m_age||this->m_name!=r.m_name;
}
private:
int m_age;
string m_name;
};
int main(void)
{
Human a(22,"z"),b(23,"g");//非常左值
const Human c(24,"j"),d(25,"w");//常左值
cout<<( a==b )<<endl;;
cout<<( a!=b )<<endl;;
cout<<(c==d)<<endl;;
cout<<(c!=d)<<endl;;
cout<<(Human(23,"zgj")==Human(22,"wr"))<<endl;
cout<<(Human(23,"zgj")!=Human(22,"wr"))<<endl;
return 0;
}
单目操作符的重载
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名"):m_age(age),m_name(name)
{
}
void getinfo()
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
Human operator-()const
{
return Human(-this->m_age,this->m_name.c_str());
}
private:
int m_age;
string m_name;
};
int main(void)
{
Human a(22,"z"),b(23,"g");//非常左值
const Human c(24,"j"),d(25,"w");//常左值
Human res=-a;
res.getinfo();
res=-c;
res.getinfo();
res=-Human(20,"w");
res.getinfo();
return 0;
}
–表达式的结果为操作数本身(而非副本)
•后自增减类单目操作符:后++、后--
–操作数为非常左值
–表达式的结果为右值,且为自增减以前的值
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名"):m_age(age),m_name(name)
{
}
void getinfo()
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
Human& operator++()
{
this->m_age+=1;
return *this;
}
Human operator++(int)
{
Human old=*this;
this->m_age+=1;
return old;
}
private:
int m_age;
string m_name;
};
int main(void)
{
Human a(22,"z"),b(23,"g");//非常左值
const Human c(24,"j"),d(25,"w");//常左值
(++a).getinfo();
(b++).getinfo();
b.getinfo();
return 0;
}
输入输出操作符的重载
•输出操作符:<<
左操作数的类型为ostream,若以成员函数形式重载该操作符,就应将其定义为ostream类的成员,该类为标准库提供,无法添加新的成员,因此只能以全局函数形式重载该操作符
ostream& operator<< (ostream& os,const RIGHT& right) { … }
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名"):m_age(age),m_name(name)
{
}
void getinfo()
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
private:
int m_age;
string m_name;
friend ostream& operator<<(ostream& os,const Human &that);
};
ostream& operator<<(ostream& os,const Human &that)
{
os<<"姓名:"<<that.m_name<<",年龄:"<<that.m_age;
return os;
}
int main(void)
{
Human a(22,"z"),b(23,"g");//非常左值
const Human c(24,"j"),d(25,"w");//常左值
cout<<a<<endl;
cout<<c<<endl;
cout<<Human(45,"zgj")<<endl;;
return 0;
}
istream & operator>> ( istream & is,RIGHT& right) { … }
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="无名"):m_age(age),m_name(name)
{
}
void getinfo()
{
cout<<"姓名:"<<this->m_name<<",年龄:"<<this->m_age<<endl;
}
private:
int m_age;
string m_name;
friend ostream& operator<<(ostream& os,const Human &that);
friend istream& operator>>(istream& is,Human& that);
};
ostream& operator<<(ostream& os,const Human &that)
{
os<<"姓名:"<<that.m_name<<",年龄:"<<that.m_age;
return os;
}
istream& operator>>(istream& is,Human& that)
{
is>>that.m_name>>that.m_age;
return is;
}
int main(void)
{
Human a(22,"z"),b(23,"g");//非常左值
const Human c(24,"j"),d(25,"w");//常左值
cout<<a<<endl;
cout<<c<<endl;;
cout<<Human(45,"zgj")<<endl;
cin>>a;
cout<<a<<endl;
return 0;
}
下标操作符
–非常容器的元素为非常左值,常容器的元素为常左值
#include <iostream>
using namespace std;
class Stack
{
public:
Stack():m_size(0)
{
}
void push(int data)
{
arr[m_size++]=data;
}
int pop()
{
return arr[--m_size];
}
int size()
{
return m_size;
}
int& operator[](size_t index)//非常函数
{
return this->arr[index];
}
const int& operator[](/*const Stack* this*/size_t index)const//常函数
{
return this->arr[index];
}
private:
int arr[20];
int m_size;
};
int main(void)
{
Stack s;//非 常容器
for(int i=0;i<20;i++)
{
s.push(1000+i);
}
cout<<"压栈后s容器中数据的个数:"<<s.size()<<endl;
s[5]=88;
for(int i=0;i<20;i++)
{
cout<<s[i]<<' ';
}
cout<<endl<<"随机查看数据后s容器数据的个数:"<<s.size()<<endl;
const Stack cs;//常容器
/* cs[5]=66;//error read-only*/
return 0;
}