目录
1 类对象的复制与赋值
1.1 举例说明
通过对字符串操作的自定义类String来说明:
class String{
private:
char *m_str;
public:
String(const char *str = NULL);
~String();
String(const String& other);
String& operator =(const String& other);
};
1.2 类对象的复制
String str1("I am young");
String str2 = str1; //有类型,在创建一个新的变量
创建一个新对象,并用同一类型的对象给它赋初值,这个过程为类对象的复制。
此时系统会自动调用类的复制构造函数进行新对象的初始化。
1.3 类对象的赋值
String str1("I am young");
String str2("hello world");
str2 = str1;
对象已经创建,创建对象时系统自动调用构造函数进行数据成员的初始化。
用另外一个对象给已存在的对象赋值。这个过程为类对象的赋值。此时会调用
类的复制操作符重载函数。
#include <iostream>
using namespace std;
class String
{
private:
char *m_str;
public:
String(const char *str = NULL); //注意默认构造函数后的默认值只需写一次,多了不行
~String();
String(const String& otherString);
String& operator =(const String& other);
void get_str(void);
};
//默认构造函数
String::String(const char *str) //返回(空)+ 作用域+函数名 小疑问:是否形参需要添加“NULL”
{
if (NULL == str)
{
m_str = new char[1];
*m_str = '\0';
}
else
{
m_str = new char[strlen(str)+1]; //代表申明一块空间
strcpy(m_str,str);
}
cout<<"Default"<<endl;
}
//复制构造函数
String::String(const String& otherString):m_str(new char[strlen(otherString.m_str)+1]) //可以使用初始化列表的形式
{
//不用判断ohterString中m_str是否为NULL
//m_str = new char[strlen(otherString.m_str)+1];
strcpy(m_str,otherString.m_str);
cout<<"copy"<<endl;
}
//析构函数
String::~String()
{
delete []m_str;
m_str = NULL;
cout<<"Destrustor"<<endl;
}
void String::get_str(void)
{
cout<<"String::"<<m_str<<endl;
}
//复制操作符重载函数
String& String::operator=(const String& otherString) //operator= 作为一个函数名来处理
{
//1、s3里面清空,把s1复制进去
//2、s3删除掉,s3指向新的空间,并把s1复制进去
if(m_str != otherString.m_str) //判断是否为s1 = s1进入函数,是则不进入if,直接返回s1 s1 = s1
{
delete []m_str;
m_str = NULL;
m_str = new char[otherString.m_str+1];
strcpy(m_str,otherString.m_str);
cout<<"operator="<<endl;
}
return *this;
}
int main()
{
String s1("Teacher is Good!");
String s2 = s1; //复制构造
s1.get_str();
String s3("xxxxx");
s3 = s1; //s3.m_str = s1.m_str; //指向同一块空间,double free
s3.get_str();
}
1.4 知识要点
(1)二者的主要区别在于对象是否已被创建,创建的同时赋初值,即执行初始化过程,即类对象的复制
已被创建,修改原来被创建对象为新的对象,执行赋值过程,即类对象的赋值。
(2)如果在类内没有显示定义复制构造函数和赋值操作符重载,则编译器会自动的合成一个复制构造函数和一个赋值操作符重载函数。
(3)类的成员中有指针变量,则必须显式定义析构函数,复制构造函数和赋值操作符重载函数、
(4)一个重要的规则:如果一个类必须定义析构函数,则它也必须定义复制构造函数和赋值操作符重载函数。
2 内嵌对象
2.1 什么是内嵌对象
一个类的成员是另外某个类的对象时,该对象成为内嵌对象。
Class A
{
int m_data1;
};
class B
{
A m_a;
int m_data2;
};
m_a就称为类 B的内嵌对象,也就是对象成员。
2.2 内嵌对象如何初始化
类包含对象成员的时候,类的构造函数要实现对象成员的初始化。
在建立类B的对象时调用类B的构造函数:
(1) 如果类A有定义默认构造函数,在执行类B的构造函数体之前,内嵌对象成员m_a自动调用类A的默认构造函数进行初始化。
(2)如果类A没有定义默认构造函数,则必须使用初始化列表的方式来初始化内嵌对象成员。
必须使用初始化列表初始化的数据类型有:
1)const成员
2)引用成员
3)无默认构造函数的内嵌对象成员
2.3 课堂练习
#include<iostream>
using namespace std;
class Point
{
private:
int m_x;
int m_y;
public:
Point(int x=0,int y=0):m_x(x),m_y(y)
{
cout << "Point Constructor" << endl;
}
Point(const Point& p):m_x(p.m_x),m_y(p.m_y)
{
cout << "Point Copy" << endl;
}
Point & operator=(const Point& p)
{
m_x = p.m_x;
m_y = p.m_y;
cout << "Point operator=()" << endl;
}
void display()
{
cout << "m_x:" << m_x << endl;
cout << "m_y:" << m_y << endl;
}
};
class Circle
{
private:
Point p1;
double m_r;
public:
Circle(int x,int y,double r):p1(x,y),m_r(r)
{
cout << "Circle Constructor" << endl;
}//不能调用自动生成的Point构造函数,需显示定义Point构造函数
/*
Circle(const Circle& c):p1(c.p1),m_r(c.m_r)//Point p1(c.p1);
{
cout << "Circle Copy" << endl;
}//可以调用系统自动生成的Point复制构造函数
*/
Circle(const Circle& c)//这么写,则至此已经开始创建p1对象
{
p1 = c.p1;//调用Point 自动生成的赋值操作符重载函数
m_r = c.m_r;
}
void display()
{
p1.display();
cout << "m_r:" << m_r << endl;
}
};
int main()
{
Circle c1(1,2,5.2);
c1.display();
Circle c2 = c1;//调用Point复制构造函数和Circle复制构造函数
c2.display();
}
内容回顾
(1)类对象的复制和赋值分别自动调用什么函数进行?
(2)类中如果需要自定义析构函数,它需要定义复制构造函数吗?
(3)必须使用初始化列表进行初始化的数据成员有哪些?
3 指向类对象的指针
指向类对象的指针的值为该类对象空间的起始地址。
class Time
{
public:
int hour;
int min;
int sec;
int get_hour();
};
Time t1;
Time *pt1 = &t1;
(*pt1).hour;
pt1->hour;
Time *pt2 = new Time;
pt2->hour;
delete pt2;
4 指向类对象数据成员的指针
定义的形式:
数据成员类型名 *指针变量名
Time t;
int *p1 = NULL;
p1 = &t.hour;
cout << *p1 << endl;
p1表示指向Time类对象t的成员hour的指针。
5 指向类对象成员函数的指针
5.1 指向普通函数的指针定义与使用
int func(int);
int (*p)(int);
p = func;
int b = func(1);
int c = (*p)(1);
int d = p(1);
指针p指向一个有一个int类型的形参且返回值为int类型的函数。
5.2 成员函数指针的定义与使用
成员函数是类中的成员,要求声明成员函数所属的类。
Time t;
int (Time::*p2)(void);
p2 = &Time::get_hour;
(t.*p2)();
p2为指向Time类的公有成员函数的指针变量,这个成员函数无形参,且返回值为int。
成员函数的指针必须在三个方面与它所指的函数的类型相匹配;
(1)函数形参的类型和数目,注意const关键字的使用。
(2)返回值类型。
(3)所属类的类型。
课堂练习
#include <iostream>
using namespace std;
int func(int x,int y)
{
return x<y?x:y;
}
class Time
{
private:
int hour;
int min;
int sec;
public:
Time(int h,int m,int s):hour(h),min(m),sec(s)
{
cout<<"Time()"<<endl;
}
void display()
{
cout<<"hour: "<<hour<<endl;
}
};
int main()
{
//1. 方式一
int (*p)(int x, int y);
p = func;
cout<<(*p)(1,2)<<endl;
//2. 方式二
typedef int (*funcp)(int,int);
funcp p1 = func;
cout<<p1(2,3)<<endl;
//类的成员函数指针
//1.
void (Time::*pt1)();
Time t(20,3,1);
pt1 = &Time::display; //指针指向对象函数的时候注意不嫩加形参 ,即 pt1 = &Time::display()错误
(t.*pt1)();
//2.
typedef void (Time::*funp)();
funp pt2 = &Time::display;
(t.*pt2)();
}
6 对象数组
6.1 对象数组的初始化
对象数组的每一个元素都是同类的对象。
(1)初始化方式:
Student stud[3] =
{
Student(参数列表),
Student(参数列表),
Student(参数列表)
}
(2)对于对象数组中的对象所属的类,该类最好定义默认的构造函数。
6.2 课堂练习
内容回顾
(1)成员函数指针与普通函数指针在定义时有什么区别?
(2)对象数组如何初始化?
(3)动态申请的对象数组,其元素所属的类是否需要定义默认构造函数?