C++基础知识学习笔记
cout输出指定格式的数据
使用cout输出指定格式的数据时,可以通过C++标准库提供的标志位和操作符控制格式,这些操作符位于iomanip头文件,使用时需要引入iomanip头文件。
1、输出八进制、十进制、十六进制数据。
#include<iostream>
using namespace std;
int main()
{
int a = 10;
cout << "oct:" << oct << a << endl; //以八进制数出a
cout << "dec:" << dec << a << endl; //以十进制输出a
cout << "hex:" << hex << a << endl; //以十六进制输出a
return 0;
}
2、输出指定精度数据。
使用setprecision()函数可以设置浮点数输出所需的精度数据,示例代码如下所示:
#include<iostream>
#include <iomanip>
using namespace std;
int main()
{
double f = 3.1415926;
cout << "默认输出:" << f << endl;
//使用setprecision(n)可控制输出流显示浮点数的数字个数。
cout << "精度控制" << setprecision(7) << f << endl;
//setprecision(n)与setiosflags(ios::fixed)合用,可以控制小数点右边的数字个数。
cout << "精度控制" << setprecision(7)
<< setiosflags(ios::fixed) << f << endl;
return 0;
}
3、输出指定域宽、对齐方式、填充方式的数据。
C++提供了setw()函数用于指定域宽,setiosflags()函数用于设置对齐方式,setfill()函数用于设置填充方式。
下面通过案例演示如何输出指定域宽、对齐方式以及填充方式的数据,如:
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
//默认是右对齐,左边空余部分用空格填充,域宽为10
cout<<setw(10)<<3.1415<<endl;
//左边空余部分用0填充
cout<<setw(10)<<setfill('0')<<3.1415<<endl;
//左对齐,右边空余部分用0填充
cout<<setw(10)<<setfill('0')
<<setiosflags(ios::left)<<3.1415<<endl;
//右对齐,左边空余部分用-填充
cout<<setw(10)<<setfill('-')
<<setiosflags(ios::right)<<3.1415<<endl;
return 0;
}
类型增强
<不是很清楚>C语言和C++语言都属于强类型语言,相比于C语言,C++中的类型检查更加严格,下面介绍C++对常见类型的增强。
1、常变量类型const
使用const修饰的变量称为常变量,C语言中的常变量可以通过指针修改,而C++中的常变量无法通过指针间接修改。示例代码如下所示:
const int a = 10;
int* p = &a; // 类型不兼容错误
*p = 20; //无法通过指针修改常变量
2、逻辑类型bool
C语言中没有逻辑类型,只能用非0表示真,用0表示假。C++增加了bool类型,使用true表示真,false表示假。示例代码如下所示:
bool a = false; //定义bool类型变量
bool b = true;
bool greater(int x, int y){return x > y;} // 比较x是否大于y
3、枚举类型enum
C语言中枚举类型只能是整数类型,且枚举变量可以用任意整数赋值,使用自由灵活。在C++中,枚举变量只能使用枚举常量进行赋值。下面代码在C++中是不被允许的。
enum temperature {WARM,COOL,HOT};
enum temperature t= WARM;
t=10; //错误
引用
引用是C++引入的新语言特性,它是某一变量的一个别名,使用“&”符号标识。引用的定义格式如下:
数据类型& 引用名 = 变量名;
习惯使用C语言开发的读者看到“&”符号就会想到取地址。但是在C++引用中,“&”只是起到标识的作用。
#include<iostream>
using namespace std;
int main()
{
int a=10;
int& ra=a;
cout<<"变量a的地址"<<hex<<&a<<endl;
cout<<"引用ra的地址:"<<hex<<&ra<<endl;
cout<<"引用ra的值:"<<dec<<ra<<endl;
return 0;
}
在定义引用时,有以下几点需要注意:
1)引用在定义时必须初始化,且与变量类型保持一致。
2)引用在初始化时不能绑定常量值,如int&b=10是错误的。
3)引用在初始化后,其值不能再更改,即不能用作其他变量的引用。
在C++中,引用的一个重要作用是作为函数参数。下面通过案例演示引用作为函数参数的用法,如:
#include<iostream>
using namespace std;
void exchange(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
int main()
{
int a, b;
cout << "please input two nums: " << endl;
cin >> a >> b;
exchange(a, b);
cout << " exchange: " << a << " "<< b << endl;
return 0;
}
/*
* exchange()函数的形参如果为普通变量(值传递),由于副本机制无法实现变量a、b的交换。
* 如果形参为指针(址传递),可以完成变量a、b的交换,但需要为形参(指针)分配存储单元,
* 在调用时要反复使用“*指针名”获取数据,且实参传递时要取地址(&a、&b),
* 这样很容易出现错误,且程序的可读性也会下降。
* 而使用引用作为形参,就克服了值传递和址传递的缺点,
* 通过引用可以直接操作变量,简单高效,可读性又好。
*/
引用是隐式的指针,但引用却不等同于指针,使用引用与使用指针有着本质的区别:
(1)指针指向一个变量,需要占据额外的内存单元,而引用指向一个变量,不占据额外内存单元。
(2)作为函数参数时,指针的实参是变量的地址,而引用的实参是变量本身,但系统向引用传递的是变量的地址而不是变量的值。
在C语言中只能用指针来处理的问题,在C++中可以通过引用完成。
如果想使用常量值初始化引用,则引用必须用const修饰,用const修饰的引用称为const引用,也称为常引用。
const引用可以用const对象和常量值进行初始化。示例代码如下所示:
const int &a = 10;
const int b = 10;
const int &rb = b;
当引用作函数参数时,也可以使用const修饰,表示不能在函数内部修改参数的值。例如下面的函数,比较两个字符串长度:
//isLonger()函数中,只能比较两个字符串长度而不能改变字符串内容
bool isLonger(const string &s1, const string &s2)
{
return s1.size() > s2.size();
}
字符串类
1.访问字符串中的字符
string类重载了“[]”运算符,可以通过索引方式访问和操作字符串中指定位置的字符。示例代码如下所示:
string s="hello,C++";
s[7]='P';
s[8]='P';
上述代码中,通过索引将字符串s中的两个“+”都修改成了’P’。
2.字符串的连接
在C语言中,连接两个字符串要调用strcat()函数,还要考虑内存溢出情况。在C++中,string重载了“+”运算符,可以使用“+”运算符连接两个string类型的字符串,示例代码如下所示:
string s1,s2;
s1="我在学习";
s2="C++";
cout<<s1+s2<<endl; //我在学习C++
3.字符串的比较
在C语言中,比较两个字符串是否相等需要调用strcm p()函数,而在C++中,可以直接调用重载的“>”“<”“==”等运算符比较两个string字符串。示例代码如下所示:
string s1,s2;
cin>>s1>>s2;
//比较两个字符串内容是否相同
if(s1>s2)
cout<<"字符串s1大于s2"<<endl;
else if (s1<s2)
cout<<"字符串s2大于s1"<<endl;
else
cout<<"字符串s1与s2相等"<<endl;
上述代码通过“>”“<”“==”运算符比较用户输入的两个字符串的内容是否相同。
4.字符串的长度计算
string类提供的length()函数用于获取字符串长度。length()函数类似于C语言中的strlen()函数。调用length()函数获取字符串长度的示例代码如下所示:
string s="hello C++";
cout<<"length():"<<s.length()<<endl;
需要注意的是,由于计算结果不包括字符串末尾结束标志符“\0”,因此,上述代码使用length()函数计算出字符串s的长度为9。
5.字符串交换
string类提供了成员函数swap(),用于交换两个字符串的值,示例代码如下所示:
string s1="hello C++";
string s2="I Love China!";
s1.swap(s2); //通过“.”运算符方式交换
swap(s1,s2); //通过函数调用方式交换
需要注意的是,string的成员函数swap()只能交换string类型的字符串,不能交换C语言风格的字符串。
new/delete
C++增加了new运算符分配堆内存,delete运算符释放堆内存。
1.使用new运算符分配堆内存
new运算符用于申请一块连续的内存,格式如下:
new 数据类型(初始化列表);
如果内存申请成功,则new返回一个具体类型的指针;如果内存申请失败,则new返回NULL。
new的用法:
(1)创建基本数据类型对象。
使用new创建基本数据类型对象,示例代码如下所示:
char* pc = new char; //存储char类型的数据
int* pi = new int(10); //存储int类型的数据
double* pd = new double(); //存储double类型的数据
(2)创建数组类型对象。
使用new创建数组对象,格式如下所示:
new 数据类型[数组长度];
使用new创建数组的示例代码如下所示:
char* pc = new char[10];
2.使用delete运算符释放堆内存
用new运算符分配的内存在使用后要及时释放以免造成内存泄漏,C++提供了delete运算符释放new出来的内存空间,格式如下:
delete 指针名;
由上述格式可知,delete运算符直接作用于指针就可以释放指针所指向的内存空间。但是使用delete运算符释放数组对象时要在指针名前加上[],格式如下:
delete[]指针名;
#include<iostream>
using namespace std;
int main()
{
int* pi = new int(10); //创建一个int对象,初始值为10
cout<<"*pi="<<*pi<<endl;
*pi = 20; //通过指针改变内存中的值
cout<<"*pi = "<<*pi<<endl;
//创建一个大小为10的char类型的数组
char* pc = new char[10];
for(int i = 0;i < 10;i++)
pc[i] = i + 65; //向数组中存入元素
for(int i = 0;i < 10;i++)
cout<<pc[i]<<" ";
cout<<endl;
delete pi; //释放int对象
delete []pc; //释放char数组对象
return 0;
}
extern"C"
<具体不是很懂>可以使用extern"C"标注C语言代码,编译器会将extern"C"标注的代码以C语言的方式编译。
强制类型转换<不理解>
1、static_cast(expression)
static_cast<>是最常用的类型转换运算符,主要执行非多态的转换,用于代替C语言中通常的转换操作。static_cast<>可以实现下列转换。
1)基本数据类型之间的转换。
2)将任何类型转换为void类型。
3)把空指针转换成目标类型的指针。
4)用于类层次结构中基类和派生类之间指针或引用的转换。向上转换(派生类转换为基类)是安全的;向下转换(基类转换为派生类)没有动态类型检查,是不安全的。
使用static_cast<>运算符进行类型转换的示例代码如下所示:
int a=1;
float b=3.14;
a=static_cast<int>(b); //将float类型转换为int类型
b=static_cast<float>(a); //将int类型转换为float类型
int *q=NULL;
void* p = NULL;
q=p; //将空指针转换为int类型,C语言允许,C++不允许
p=q;
q=static_cast<int*>(p); //将空指针转换为int类型指针
2、reinterpret_cast(expression)
reinterpret_cast通常为操作数的位模式提供较低层的重新解释。例如,如果将一个int类型的数据a转换为double类型的数据b,仅仅是将a的比特位复制给b,不作数据转换,也不进行类型检查。reinterpret_cast要转换的类型必须是指针类型、引用或算术类型。
使用reinterpret_cast<>运算符进行类型转换的示例代码具体如下:
char c = 'a';
int d = reinterpret_cast<int&>(c);
int *p=NULL;
float *q=NULL;
p = q; //C 语言允许,C++语言不允许
q = p; //C 语言允许,C++语言不允许
p = static_cast<int*>(q); //static_cast无法转换
q = static_cast<int*>(p); //static_cast无法转换
p = reinterpret_cast<int*>(q);
q = reinterpret_cast<float*>(p)
3、const_cast(expression)
const_cast<>用于移除const对象的引用或指针具有的常量性质,可以去除const对引用和指针的限定。示例代码如下所示:
需要注意的是。const_cast<>只能用于转换指针或引用。
int num = 100;
const int* p1 = #
//将常量指针转换为普通类型指针,去除const属性
int* p2 = const_cast<int*>(p1);
*p2 = 200;
int a=100;
const int & ra=a;
//将常量引用转换为普通类型引用,去除const属性
const_cast<int&>(ra)=200;
4、dynamic_cast(expression)
dynamic_cast<>用于运行时检查类型转换是否安全,可以在程序运行期间确定数据类型,如类的指针、类的引用和void*。dynamic_cast<>主要应用于类层次间的向上转换和向下转换,以及类之间的交叉转换。在类层次间进行向上转换时,它和static_cast作用一致。不过,与static_cast相比,dynamic_cast能够在运行时检查类型转换是否安全。
当向下转换时,如果基类指针或引用指向派生类对象,dynamic_cast运算符会返回转换后类型的指针,这样的转换是安全的。如果基类指针或引用没有指向派生类对象,则转换是不安全的,转换失败时就返回NULL。
类与对象
类的定义
class 类名
{
权限控制符:
成员;
};
返回值类型 类名::函数名称(参数列表)
{
函数体
}
}
eg:
定义:
class Student //定义学生类Student
{
public: //公有权限
void study(); //声明表示学习的成员函数
void exam(); //声明表示考试的成员函数
private: //私有权限
string _name; //声明表示姓名的成员变量
int _age; //声明表示年龄的成员变量
};
使用:
不加·”类型::“的话,则为一个普通的函数。
void Student::study() //类外实现study()成员函数
{
cout << "学习C++" << endl;
}
void Student::exam() //类外实现exam()成员函数
{
cout << "C++考试成绩100分" << endl;
}
1、public(公有类型):被public修饰的成员也称为公有成员。公有成员是类的外部接口,可以被所属类的成员函数、类对象、派生类对象、友元函数、友元类访问。
2、protected(保护类型):被protected修饰的成员称为保护成员,其访问权限介于私有和公有之间,可以被所属类的成员函数、派生类对象、友元类和友元函数访问。
3、private(私有类型):被private修饰的成员称为私有成员,只能被所属类的成员函数、友元函数、友元类访问。
对象的创建与使用
定义了类,就相当于定义了一个数据类型。类与int、char等数据类型的使用方法是一样的,可以定义变量,使用类定义的变量通常称为该类的对象。
对象的定义:
类名 对象名;
下面通过案例演示类的定义、对象的创建及对象的成员访问,如例2-1所示。
#include<iostream>
using namespace std;
class Student //定义学生类Student
{
public: //公有类型
void study(); //声明表示学习的成员函数
void exam(); //声明表示考试的成员函数
string _name; //声明表示姓名的成员变量
int _age; //声明表示年龄的成员变量
};
void Student::study() //类外实现study()成员函数
{
cout << "学习C++" << endl;
}
void Student::exam() //类外实现exam()成员函数
{
cout << "C++考试成绩100分" << endl;
}
int main()
{
Student stu; //创建Student类对象stu
stu._name = "张三"; //设置对象stu的姓名
stu._age = -20; //设置对象stu的年龄
cout << stu._name << stu._age << "岁" << endl;
stu.study(); //调用study()成员函数
stu.exam(); //调用exam()成员函数
return 0;
}
小提示:new创建类对象
类是自定义数据类型,与基本数据类型的使用方式相同,也可以使用new创建类对象。例如,例2-1定义的Student类,可以使用new创建Student类对象,示例代码如下所示:
Student* ps = new Student; //使用new创建类对象
//…其他功能代码
delete ps; //使用delete释放对象
构造函数
构造函数其定义格式如下:
class 类名
{
权限控制符:
构造函数名(参数列表)
{
函数体
}
... //其他成员
};
关于构造函数定义格式的说明,具体如下:
(1)构造函数名必须与类名相同。
(2)构造函数名的前面不需要设置返回值类型。
(3)构造函数中无返回值,不能使用return返回。
(4)构造函数的成员权限控制符一般设置为public。
<注意>:如果在类中提供了自定义构造函数,编译器便不再提供默认构造函数。自定义构造函数时,可以定义无参构造函数,也可以定义有参构造函数,下面分别进行讲解。
1、自动逸无参构造函数
自定义无参构造函数时,可以在函数内部直接给成员变量赋值。下面通过案例演示无参构造函数的定义与调用。
#include<iostream>
#include<iomanip>
using namespace std;
class Clock //定义时钟类Clock
{
public:
Clock(); //声明无参构造函数
void showTime(); //声明显示时间的成员函数
private:
int _hour; //声明表示小时的成员变量
int _min; //声明表示分钟的成员变量
int _sec; //声明表示秒的成员变量
};
Clock::Clock() //类外实现无参构造函数
{
_hour=0; //初始化过程,将成员变量初始化为0
_min=0;
_sec=0;
}
void Clock::showTime() //类外实现成员函数
{
cout<<setw(2)<<setfill('0')<<_hour<<":"
<<setw(2)<<setfill('0')<<_min<<":"
<<setw(2)<<setfill('0')<<_sec<<endl;
}
int main()
{
Clock clock; //创建对象clock
cout<<"clock:";
clock.showTime(); //通过对象调用成员函数showTime()显示时间
return 0;
}
2、自定义有参构造函数
如果希望在创建对象时提供有效的初始值,可以通过定义有参构造函数实现。
#include<iostream>
#include<iomanip>
using namespace std;
class Clock //定义时钟类Clock
{
public:
Clock(int hour, int min, int sec); //声明有参构造函数
void showTime(); //用于显示时间的成员函数
private:
int _hour; //声明表示小时的成员变量
int _min; //声明表示分钟的成员变量
int _sec; //声明表示秒的成员变量
};
Clock::Clock(int hour, int min, int sec) //类外实现有参构造函数
{
_hour=hour; //初始化过程,将初始值直接赋值给成员变量
_min=min;
_sec=sec;
}
void Clock::showTime() //类外实现成员函数
{
cout<<setw(2)<<setfill('0')<<_hour<<":"
<<setw(2)<<setfill('0')<<_min<<":"
<<setw(2)<<setfill('0')<<_sec<<endl;
}
int main()
{
Clock clock1(10,20,30); //创建对象clock1,传入初始值
cout<<"clock1:";
clock1.showTime(); //通过对象调用成员函数showTime()显示时间
Clock clock2(22,16,12); //创建对象clock2,传入初始值
cout<<"clock2:";
clock2.showTime(); //通过对象调用成员函数showTime()显示时间
return 0;
}
需要注意的是,在实现构造函数时,除了在函数体中初始化成员变量,还可以通过“:”运算符在构造函数后面初始化成员变量,这种方式称为列表初始化,其格式如下所示:
类::构造函数(参数列表): 成员变量1(参数1), 成员变量2(参数2),…, 成员变量n(参数n)
{
构造函数体
}
eg:
Clock::Clock(int hour, int min, int sec):_hour(hour),_min(min),_sec(sec)
{
//...
}
<重载构造函数>
在C++中,构造函数允许重载。
<含有成员对象的类的构造函数>
C++允许将一个对象作为另一个类的成员变量,即类中的成员变量可以是其他类的对象,这样的成员变量称为类的子对象或成员对象。含有成员对象的类的定义格式如下所示:
class B
{
A a; //对象a作为类B的成员变量
... //其他成员
}
创建含有成员对象的对象时,先执行成员对象的构造函数,再执行类的构造函数。例如,上述格式中,类B包含一个类A对象作为成员变量,在创建类B对象时,先执行类A的构造函数,将类A对象创建出来,再执行类B的构造函数,创建类B对象。如果类A构造函数有参数,其参数要从类B的构造函数中传入,且必须以“:”运算符初始化类A对象。
在类中包含对象成员,能够真实地描述客观事物之间的包含关系,比如描述学生信息的类,类中的成员除了姓名、学号属性,还包含出生日期。在定义学生类的时候,可以先定义一个描述年、月、日的出生日期类,再定义学生类,将出生日期类的对象作为学生类的成员变量。
#include<iostream>
using namespace std;
class Birth //定义出生日期类Birth
{
public:
Birth(int year,int month, int day); //构造函数
void show(); //声明成员函数show()显示日期
private:
int _year;
int _month;
int _day;
};
//类外实现构造函数
Birth::Birth(int year, int month, int day)
:_year(year),_month(month),_day(day)
{
cout<<"Birth类构造函数"<<endl;
}
//类外实现show()函数
void Birth::show()
{
cout<<"出生日期:"<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
class Student //定义学生类Student
{
public:
//构造函数
Student(string name, int id, int year, int month, int day);
void show();
private:
string _name;
int _id;
Birth birth;
};
//类外实现构造函数
Student::Student(string name, int id, int year, int month, int day)
:birth(year,month,day)
{
cout<<"Student类构造函数"<<endl;
_name=name;
_id=id;
}
//类外实现show()函数
void Student::show()
{
cout<<"姓名:"<<_name<<endl;
cout<<"学号:"<<_id<<endl;
birth.show();
}
int main()
{
Student stu("lili",10002,2000,1,1); //创建学生对象stu
stu.show(); //显示学生信息
return 0;
}
析构函数
在C++中,对象资源的释放通过析构函数完成。析构函数的作用是在对象被释放之前完成一些清理工作。析构函数调用完成之后,对象占用的资源也被释放。
与构造函数一样,析构函数也是类的一个特殊成员函数,其定义格式如下所示:
class 类名
{
~析构函数名称();
... //其他成员
}
<注意>
1、析构函数的名称与类名相同,在析构函数名称前添加“~”符号。
2、析构函数没有参数。因为没有参数,所以析构函数不能重载,一个类中只有一个析构函数。
3、析构函数没有返回值,不能再析构函数名称前添加任何返回值类型。在析构函数内部,也不能return返回任何值。
4、当程序结束时,编译器会自动调用析构函数完成对象的清理工作,如果类中没有定义析构函数,编译器会提供一个默认的析构函数,但默认的析构函数只能完成栈内存对象的资源清理,无法完成堆内存对象的资源清理。因此,在程序中往往需要自动逸析构函数。
析构函数的调用情节狂主要有一下集中:
①在一个函数中定义了一个对象,当函数调用结束时,对象应当被释放,对象释放之前编译器会调用析构函数释放资源。
②对于static修饰的对象和全局对象,只有在程序结束时编译器才会调用析构函数。
③对于new运算符创建的对象,在调用delete释放时,编译器会调用析构函数释放资源。
<注意>
析构函数的调用顺序与构造函数的调用顺序是相反的。在构造对象和析构对象时,C++遵循的原则是:先构造的后析构,后构造的先析构。例如,连续创建了两个对象A1和A2,在创建时,先调用构造函数构造对象A1,再调用构造函数构造对象A2;在析构时,先调用析构函数析构对象A2,再调用析构函数析构对象A1。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <cstring>
using namespace std;
class Rabbit //定义兔子类Rabbit
{
public:
Rabbit(string name,const char* pf); //声明构造函数
void eat();
~Rabbit(); //声明析构函数
private:
string _name; //声明表示兔子名字的成员变量
char* _food; //声明表示兔子食物的成员变量
};
Rabbit::Rabbit(string name, const char* pf)
{
cout<<"调用构造函数"<<endl;
_name=name;
_food=new char[50]; //为_food指针申请空间
memset(_food,0,50); //初始化_food空间
strcpy(_food,pf); //将参数pf指向的数据复制到_food中
}
void Rabbit::eat()
{ //类外实现成员函数
cout<<_name<<" is eating "<<_food<<endl;
}
Rabbit::~Rabbit() //类外实现析构函数
{
cout<<"调用析构函数,析构"<<_name<<endl;
if(_food != NULL)
delete []_food;
}
int main()
{
Rabbit A("A","luobo");
A.eat();
Rabbit B("B","baicai");
B.eat();
return 0;
}
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它具有构造函数的所有特性,并且使用本类对象的引用作为形参,能够通过一个已经存在的对象初始化该类的另一个对象。拷贝构造函数的定义格式如下所示:
class 类名
{
public:
构造函数名称(const 类名& 对象名)
{
函数体
}
... //其他成员
};
在定义拷贝构造函数时,为了使引用的对象不被修改,通常使用const修饰引用的对象。
#include<iostream>
using namespace std;
class Sheep //定义绵羊类Sheep
{
public:
Sheep(string name,string color); //声明有参构造函数
Sheep(const Sheep& another); //声明拷贝构造函数
void show(); //声明普通成员函数
~Sheep(); //声明析构函数
private:
string _name; //声明表示绵羊名字的成员变量
string _color; //声明表示绵羊颜色的成员变量
};
Sheep::Sheep(string name, string color)
{
cout<<"调用构造函数"<<endl;
_name=name;
_color=color;
}
Sheep::Sheep(const Sheep& another) //类外实现拷贝构造函数
{
//在函数体中,将形参sheepA的成员变量值赋给类的成员变量
cout<<"调用拷贝构造函数"<<endl;
_name=another._name;
_color=another._color;
}
void Sheep::show()
{
cout<<_name<<" "<<_color<<endl;
}
Sheep::~Sheep()
{
//程序运行结束之后,调用析构函数先析构对象sheepB,然后析构对象sheepA
cout<<"调用析构函数"<<endl;
}
int main()
{
Sheep sheepA("Doly","white");
cout<<"sheepA:";
sheepA.show();
Sheep sheepB(sheepA); //使用sheepA初始化新对象sheepB,在这个过程中编译器会调用拷贝构造函数
cout<<"sheepB:";
sheepB.show();
return 0;
}
当涉及对象之间的赋值时,编译器会自动调用拷贝构造函数。拷贝构造函数的调用情况有以下三种。
(1)使用一个对象初始化另一个对象。上述例子就是使用一个对象初始化另一个对象。
(2)对象作为参数传递给函数。当函数的参数为对象时,编译器会调用拷贝构造函数将实参传递给形参。
(3)函数返回值为对象。当函数返回值为对象时,编译器会调用拷贝构造函数将返回值复制到临时对象中,将数据传出。
浅拷贝
拷贝构造函数是特殊的构造函数,如果程序没有定义拷贝构造函数,C++会提供一个默认的拷贝构造函数,默认拷贝构造函数只能完成简单的赋值操作,无法完成含有堆内存成员数据的拷贝。例如,如果类中有指针类型的数据,默认的拷贝构造函数只是进行简单的指针赋值,即将新对象的指针成员指向原有对象的指针指向的内存空间,并没有为新对象的指针成员申请新空间,这种情况称为浅拷贝。
浅拷贝在析构指向堆内存空间的变量时,往往会出现多次析构而导致程序错误。C++初学者自定义的拷贝构造函数往往实现的是浅拷贝。