C++知识点12——构造函数初步

构造函数就是定义了类的对象的初始化方式的函数,在初始化类的对象时,会被自动调用

构造函数无返回值,可以被重载(有多个构造函数,可以有多种初始化的方式,参考C++知识点4——vector与string简述

class A
{
public:
	A(int a) {cout<<"a constructor"<<endl;}
	A(){cout<<__func__<<endl;}
	~A() {}
};

int main(int argc, char const *argv[])
{
	A a;
	A b(10);
	return 0;
}

 

注意:

注意区分下面两行代码

A a;
A a();

第一个是执行默认初始化,而第二个是声明了一个函数,如果将main函数中默认初始化改为第二种形式,程序不会打印构造函数中的log

int main(int argc, char const *argv[])
{
	A a();//仅仅是声明了一个函数,内存中并没有a对象
	A b(10);
	return 0;
}

上述代码没有生成a对象,所以,更不能用A a()来调用其他成员

 

构造函数不能是const的成员函数,因为在构造函数中有可能会改变对象的成员,如果又将构造函数设为const,那么对象将无法被初始化

class A
{
public:
	A() const {}
	~A() {}
};

 

如果类中没有自己定义构造函数,那么编译器会自动生成一个构造函数(A() {}),并且类的对象在在初始化时,执行默认初始化(用默认构造函数初始化对象)

但是,最好不要使用编译器提供的默认构造函数

class A
{
public:
	int &b;
};

int main(int argc, char const *argv[])
{
	A a;
	return 0;
}

上述代码类中定义了一个引用,但是使用默认的构造函数并没有初始化引用,错过了引用初始化的唯一机会另外,导致程序报错,如果是类中定义了一个指针,也会造成未初始化的指针

 

如果想让错误消失,一种方法就是不定义引用,二是自己定义构造函数,下面代码展示了第二种办法

class A
{
public:
	A():a(10), b(a){cout<<__func__<<endl;}

	int &b;
	int a;
};

int main(int argc, char const *argv[])
{
    A a;
	cout<<a.b<<endl;
	return 0;
}

冒号后面的就是初始化列表,用逗号隔开

 

构造函数的初始化列表与初始化顺序

尽可能使用初始化列表初始化变量,因为这是初始化成员变量的唯一机会,不要在构造函数中对成员变量进行赋值,因为有些变量无法赋值

class A
{
public:
	A();
    const int a;
};

A::A()
{
	a=10;
	cout<<__func__<<endl;
}

int main(int argc, char const *argv[])
{
	A a;
	return 0;
}

上述代码在编译时提示没有初始化const int,因为该成员唯一的初始化机会就是在构造函数的初始化列表中,当在构造函数中对a进行“初始化”时,其实是赋值操作,但是const变量无法重新赋值

正确做法就是将a在初始化列表中初始化

 

构造函数初始化成员变量的顺序是按照成员定义的顺序来初始化的

class A
{
public:
	A();
	~A() {}
    const int a;
    int b;
    int c;
};

A::A():a(10),b(5),c(1)
{
	cout<<__func__<<endl;
}

int main(int argc, char const *argv[])
{
	A a;
	cout<<a.a<<a.b<<a.c<<endl;
	return 0;
}

上述代码中成员变量的初始化顺序是a,b,c(因为a最前,b次之,c最后),所以,在b初始化时,c中的值不确定,所以不能用c初始化b,将构造函数改成如下形式

A::A():a(10),b(c),c(1)
{
	cout<<__func__<<endl;
}

可见,b的值是一个无效值,因为用c的无效值初始化

所以,在构造函数进行成员函数初始化时,最好不要让成员变量彼此初始化,如果必须这样,那么请注意成员函数的初始化顺序

 

explicit关键与构造函数

explicit关键字用来修饰构造函数,加上该关键字的构造函数不能执行隐式转换,具体例子见如下代码

class A
{
public:
	/*explicit*/A(int a):mem(a) {cout<<"a constructor"<<endl;}
	A():mem(0){cout<<__func__<<endl;}
	~A() {}
	A add(const A &one, const A & two);
	int mem;
};

A A::add(const A &one, const A & two)
{
	A tmp;
	tmp.mem=one.mem+two.mem;
	return tmp;
}

int main(int argc, char const *argv[])
{
	A one,two;
	one.mem=10;
	two.mem=20;
	A res1=one.add(one, two);
	cout<<res1.mem<<endl;
	A res2=one.add(10, 20);
	cout<<res2.mem<<endl;
	return 0;
}

构造函数A(int a) 没有用explicit修饰,所以当执行第25行代码时,将10和20从int型数据隐式转化为两个类A的临时对象,并输出两次log,然后将这两个临时对象传入add函数

但是如果将构造函数A(int a)前添加explicit,那么上述代码就会报错,因为加上explicit关键字后,会防止上述过程

调用add时,提示函数匹配错误

 

此外,加上explicit修饰构造函数后,初始化对象只能用直接初始化,不能拷贝初始化

class A
{
public:
	explicit A(int a):mem(a) {cout<<"a constructor"<<endl;}
	A():mem(0){cout<<__func__<<endl;}
	~A() {}
	int mem;
};

int main(int argc, char const *argv[])
{
	A one(10);
	A two=20;
	return 0;
}

编译上述代码时,会提示转化错误

解决办法:

1.将explicit关键字去掉。2.将13行注释掉。3.使用static_cast转换,4。显式构造一个对象

解决办法3的代码

int main(int argc, char const *argv[])
{
	A one(10);
	A two=static_cast<A>(20);
	return 0;
}

 

解决办法4的代码

class A
{
public:
	explicit A(int a):mem(a) {cout<<"a constructor"<<mem<<endl;}
	A():mem(0){cout<<__func__<<endl;}
	~A() {}
	A add(const A &one, const A & two);
	int mem;
};

A A::add(const A &one, const A & two)
{
	cout<<__func__<<endl;
	A tmp;
	tmp.mem=one.mem+two.mem;
	return tmp;
}

int main(int argc, char const *argv[])
{
	A res2=res2.add(A(10), A(20));//创建两个A的对象,调用顺序从右向左
	cout<<res2.mem<<endl;
	return 0;
}

上述的输出结果为编译器优化的结果,关于编译器优化的具体内容见博客https://blog.csdn.net/davidhopper/article/details/90696200,写的很好

 

将上述代码关闭编译器优化的命令如下

 g++ -g -fno-elide-constructors -Wall constructor.cpp

输出结果如下

其中,调用了两次拷贝构造函数,第一次是函数add返回时调用的,第二次是res2初始化的时候调用的,因为在拷贝构造函数中没有进行赋值操作,所以打印结果是mem的初始值0

 

如果在拷贝构造函数中添加赋值操作

mem=t.mem;

打印结果如下

上过过程的讨论见https://bbs.csdn.net/topics/396829997

 

参考:

《C++ Primer》

https://blog.csdn.net/davidhopper/article/details/90696200

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章