【C++基础知识学习笔记】

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 = &num; 
//将常量指针转换为普通类型指针,去除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++初学者自定义的拷贝构造函数往往实现的是浅拷贝。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大不了_自己学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值