《面向对象程序设计 C++》构造函数

什么是构造函数

构造函数为类对象进行初始化。
构造函数是一种与类名相同的成员函数。
构造函数没有返回类型。
一个类可以拥有多个构造函数,也可对构造函数进行重载。
例如:

class Person
{
public:
	Person(); //默认构造函数
	Person( const string& n); //重载构造函数
	Person( const char* n);
	void setName(const string& n);
	void setName(const char* n);
	const string& getName() const;
private:
	string name;
	

当创建一个对象时,构造函数会被调用。构造函数为类提供一个特殊的成员函数,此函数在创建对象时自动被调用。
构造函数最大的特点是:函数名与类名相同,没有返回类型。除此之外,构造函数的行为与其他函数相同,也可完成如赋值,条件测试,循环,函数调用等功能。构造函数既可在类声明之中定义,也可在类声明之外定义。
在类声明时定义的构造函数为inline类型。

默认构造函数

默认构造函数没有任何参数。但如果需要,构造函数也可以带有参数。
多数情况下,编译器为类生成一个公有的默认构造函数,即类中不包含任何构造函数时,但只有下面两种情况例外:

  • 一个类显示地声明了任何构造函数,编译器不生成公有的默认构造函数。
  • 一个类声明了一个非公有的默认构造函数,编译器不生成公有的默认构造函数。
#include<iostream>
using namespace std;
class Emp
{
public:
    Emp();
private:
    int id;
};
Emp::Emp()
{
    id=0;
    cout<<"This step will be executed first!"<<endl;
}
int main()
{
    Emp a;
    Emp b;
    return 0;
}

类在声明对象后,将首先执行默认构造函数,这段代码中默认构造函数对变量id初始化。执行结果:

This step will be executed first!
This step will be executed first!

带参构造函数

默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值,在带参构造函数中,有两类很重要的构造函数,分别是拷贝构造函数转型构造函数

拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象。
  • 复制对象把它作为参数传递给函数。
  • 复制对象,并从函数返回这个对象。

如果类中没有拷贝构造函数,编译器会自动生成一个。编译器生成的这个拷贝构造函数版本完成这样的操作:将源对象所有数据成员的值逐一赋值给目标对象相应的数据成员。
拷贝构造函数有两种原型:
Person(Person& );Person(const Person& );
两种原型的参数类型都是引用
通常,如果一个类包含指向动态存储空间指针类型的数据成员,则应为这个设计拷贝构造函数
例如以下程序:

#include<iostream>
#include<string>
using namespace std;

class Namelist
{
public:
    Namelist() { size=0;p=0; } //默认构造函数
    Namelist(const string [],int );
    void set(const string& ,int );
    void set(const char*,int ); //set函数重载
    void dump() const;
private:
    int size;
    string *p;
};

Namelist::Namelist(const string s[],int si)
{
    p=new string[size=si]; //为p分配内存空间
    for(int i=0;i<size;i++) //复制构造函数将字符串拷贝给成员变量p
    {
        p[i]=s[i];
    }
}
void Namelist::set(const string& s,int i)
{
    p[i]=s;
}

void Namelist::set(const char* s,int i)
{
    p[i]=s;
}
void Namelist::dump() const
{
    for(int i=0;i<size;i++)
    {
        cout<<p[i]<<" ";
    }
    cout<<endl;
}

int main()
{
    string list[]={"Lab","Husky","Collie"};
    Namelist d1(list,3);
    cout<<"d1:"<<endl;
    d1.dump();
    Namelist d2(d1); //拷贝对象,程序中没有显示的声明拷贝构造函数,系统自动生成拷贝构造函数
    cout<<"d2:"<<endl;
    d2.dump();
    d2.set("Great Dean",1);
    cout<<"d2:"<<endl;
    d2.dump();
    cout<<"d1"<<endl; //
    d1.dump();
    return 0;
}

来看下运行结果:

d1:
Lab Husky Collie
d2:
Lab Husky Collie
d2:
Lab Great Dean Collie
d1
Lab Great Dean Collie

这段代码中,d2(d1),其目的是把d1中的内容拷贝给d2,由于我们没有定义拷贝构造函数,所以编译器提供的拷贝构造函数被调用,但之后通过set函数修改了d2中的一个数据,当我们再次输出d2,d1时,原本想象的是d2内容改变,d1内容不改变,结果最终输出结果相同,d1内容也变了。这是因为d1与d2指向了相同的存储区域所导致的,如果想达到我们的目的,我们可以
自己定义拷贝构造函数来实现。

Namelist::Namelist(const Namelist& d) //拷贝构造函数
{
    p=0;
    copyIntoP(d);
}
void Namelist::copyIntoP(const Namelist& d) 
{
    delete[] p;
    if(d.p!=0)
    {
        p=new string[size=d.size];
        for(int i=0;i<size;i++)
        {
            p[i]=d.p[i];
        }
    }
    else
    {
        p=0;
        size=0;
    }
}

完整代码:

#include<iostream>
#include<string>
using namespace std;

class Namelist
{
public:
    Namelist() { size=0;p=0; } //默认构造函数
    Namelist(const string [],int );
    Namelist(const Namelist& ); //拷贝构造函数
    void set(const string& ,int );
    void set(const char*,int ); //set函数重载
    void dump() const;
private:
    int size;
    string *p;
    void copyIntoP(const Namelist&); //通过该函数实现拷贝
};

Namelist::Namelist(const string s[],int si)
{
    p=new string[size=si];
    for(int i=0;i<size;i++)
    {
        p[i]=s[i];
    }
}
void Namelist::set(const string& s,int i)
{
    p[i]=s;
}

void Namelist::set(const char* s,int i)
{
    p[i]=s;
}
Namelist::Namelist(const Namelist& d)
{
    p=0;
    copyIntoP(d);
}
void Namelist::copyIntoP(const Namelist& d)
{
    delete[] p;
    if(d.p!=0)
    {
        p=new string[size=d.size];
        for(int i=0;i<size;i++)
        {
            p[i]=d.p[i];
        }
    }
    else
    {
        p=0;
        size=0;
    }
}
void Namelist::dump() const
{
    for(int i=0;i<size;i++)
    {
        cout<<p[i]<<" ";
    }
    cout<<endl;
}

int main()
{
    string list[]={"Lab","Husky","Collie"};
    Namelist d1(list,3);
    cout<<"d1:"<<endl;
    d1.dump();
    Namelist d2(d1); //拷贝对象,程序中没有显示的声明拷贝构造函数,系统自动生成拷贝构造函数
    cout<<"d2:"<<endl;
    d2.dump();
    d2.set("Great Dean",1);
    cout<<"d2:"<<endl;
    d2.dump();
    cout<<"d1"<<endl; //
    d1.dump();
    return 0;
}

此时再输出时:

d1:
Lab Husky Collie
d2:
Lab Husky Collie
d2:
Lab Great Dean Collie
d1
Lab Husky Collie

转型构造函数

转型构造函数用于类型间的转换,它只有一个参数。例如,类C的转型构造函数可将其他类型的变量转型为类C类型的变量,如int或string类型。
如下面的例子所示:

#include <iostream>
#include<string>
using namespace std;
class Emp
{
  public:
    Emp();
    Emp(int x);
    Emp(string ss);
    void PrintInt() const;
    void PrintString() const;

  private:
    int id;
    string s;
};
Emp::Emp()
{
    /*默认构造函数*/
    id = 0;
    s="";
}
Emp::Emp(int x)
{
    /*int类型转型构造函数,*/
    id = x;
}
Emp::Emp(string ss)
{
	/*string类型转型构造函数*/
    s=ss;
}
void Emp::PrintInt() const
{
    cout << id << endl;
}
void Emp::PrintString() const
{
    cout<<s<<endl;
}
int main()
{
    Emp a(5); 
    Emp b("Hello");
    a.PrintInt(); //5
    b.PrintString(); //Hello
    return 0;
}

转型构造函数与隐式转换

有这样一段代码,void f(Person p);类型为Person对象,将一个string作为参数来调用f,通过Person类的一个转型构造函数,编译器就可以在string对象s上调用该转型构造函数以此来构造一个Person对象作为f的参数。称Person类的转型构造函数支持隐式类型转换,即该构造函数采用隐藏方式将一个string转型为一个Person:

#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
    Person(string s);
    string get() const;
private:
    string name;
};
Person::Person(string s)
{
    name=s;
}
string Person::get() const
{
    return name;
}
void f(Person p)
{
    cout<<p.get()<<endl;
}
int main()
{
    string s="hello";
    f(s); //隐式的将string类转换为Person类
    return 0;
}

输出结果:

hello

但这种隐式转换有时会导致一些无法预料的错误,而这些错误往往细微难以察觉,为避免这种错误,可以用C++提供的关键字expplicit来实现:

#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
    explicit Person(string s); //只需要在构造函数处加上explicit即可
    string get() const;
private:
    string name;
};
Person::Person(string s)
{
    name=s;
}
string Person::get() const
{
    return name;
}
void f(Person p)
{
    cout<<p.get()<<endl;
}
int main()
{
    Person p("hihi");
    f(p); //ok,p is a Person
    string s="hello";
    f(s); //ERROR,no implicit type conversion
    return 0;
}

当再次把string传给函数f时,就会报错了,此时只能通过传递Person类给函数f作为参数才允许。

使用初始化列表来初始化字段

Emp::Emp(int x):id(x){} //成员变量id通过列表方式被初始化

其等价于:

Emp::Emp(int x)
{
    //带参数构造函数
    id=x;
}

假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:

C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
  ....
}

构造函数与操作符new和new[]

C++中为指针分配内存空间有new和new[],与C中的函数malloc和calloc一样,但又有不同,甚至C++的比C的更好,因为new和new[]在分配存储空间时,还会调用相应的构造函数,而malloc和calloc无法完成这个任务。

class Emp
{
public:
    Emp();
    Emp(const char * name);
};
int main()
{
    Emp* elvis = new Emp();
    Emp* cher = new Emp("Cher");
    Emp* lotStor = new Emp[100];
    Emp* foo = (Emp* )malloc(sizeof(Emp));
	 //...
}

在本例中,对象elvis用new创建,默认构造函数对单一Emp单元进行了初始化;对象cher用new创建,转型构造函数对单一Emp单元进行了初始化;对象lotStor用new[]创建,默认构造函数对其拥有的100个Emp单元进行初始化;而foo用malloc创建,因此它没有被初始化。

关于构造函数的大致总结完了,构造函数刚开始学时,觉得好乱,看不懂,由于期末考了,放置了两周,现在拿起来再看,也没那么难啦,或许,有时看了但还不会时,可以先放置一段时间,待大脑梳理一下,>_<

猜你喜欢

转载自blog.csdn.net/qq_41822647/article/details/84982621
今日推荐