最近在学习C++,记录一下自己的所得所悟,本人水平有限,对C++的理解有限,如果不对的地方各位可以指出来,在评论区留言,谢谢!
C++的类包含数据成员和函数成员(公有对外接口、保护或者私有的一些辅助函数),构造函数是比较特殊的函数,作用是在创建对象的时候用来初始化对象用的。构造函数也与普通函数一样支持函数重载,可以为类定义多个构造函数以满足不同条件化下初始化对象的要求。
下面是一个简单的书本类Book。
#include#include class Book { public: //构造函数重载 Book() = default; //构造函数-1 Book(const char* pc, unsigned num, double price); //构造函数-2 Book(const std::string& str, unsigned num, double price); //构造函数-3 //公有对外接口 const std::string& name() const { return m_name; } unsigned number() const { return m_number; } double price() const { return m_price; } double total() const { return m_total;} //析构函数 virtual ~Book() {} private: //C++11 支持类内初始值 //m_total的初值依赖于m_number和m_price,所以最好将m_total放在m_number和m_price之后 //类的成员初始化顺序与类中成员的声明顺序一致 std::string m_name = ""; //书名 unsigned m_number = 0; //销售量 double m_price = 0; //单价 double m_total = 0; //销售总额 }; //构造函数-2的定义 inline Book::Book(const char* pc, unsigned num, double price) : m_name(pc), m_number(num), m_price(price), m_total(num * price) { } //构造函数-3的定义 inline Book::Book(const std::string& str, unsigned num, double price) : m_name(str), m_number(num), m_price(price), m_total(num * price) { }
类的构造函数的作用其实就是在创建类的对象时,初始化数据成员的。Book类拥有四个数据成员,所以才创建Book类的对象的时候,需要为四个数据成员提供初始值。C++11支持类内初始值,所以可以在定义成员变量的时候为成员赋初始值,这样在不提供任何实参时,将调用默认构造函数使用类内初始值来初始化对象。
Book类声明并定义了三个构造函数,类的构造函数的函数名与类名相同,没有返回类型(包括void),参数列表中可以没有参数,也可有多个参数。
1. 默认构造函数
当类的设计者没有为类定义任何一个构造函数时,编译器会隐式为类生成一个没有参数的构造函数来初始化对象,但是当我们自己定义了构造函数以后,编译器就不会为类生成默认构造函数了。如果不为类设计默认构造函数,单独使用这个类可能不会有什么问题,但是在涉及到类的组合或者继承的时候,有的场景下就会比较麻烦,所以在设计类的时候,通常都要为类提供一个默认构造函数。
由于在Book类中已经声明并定义了另外两个带参数的构造函数,所以编译器就不会再为Book类提供默认构造函数了,这种情况下就需要我们手动定义一个默认的构造函数,C++11有一个很简单的新语法:Book() = default; 就是告诉编译器,虽然我自己已经定义了构造函数,但是我还需要编译器继续为Book类提供默认构造函数。创建Book对象时,如果不提供任何参数,将自动调用默认构造函数:
Book book; //自动调用默认构造函数初始化对象book Book book = Book();//显式调用默认构造函数 Book book(); //错误!这个是函数的声明,声明了一个名为book,参数列表为空,返回类型为Book的函数。
2. 带参数的构造函数
Book类中声明了两个带三个参数的构造函数,由于两个函数的参数列表不一样,构成了函数的重载。
//构造函数-2的定义 inline Book::Book(const char* pc, unsigned num, double price) : m_name(pc), m_number(num), m_price(price), m_total(num * price) { }
构造函数-2在类内声明,在类外定义成了内联函数,并使用了初始化列表来初始化成员变量,这个函数有几个需要注意的点:
(1)成员的初始化顺序是与成员在类中声明的顺序一致,与初始化列表中成员的顺序无关,如果某个成员的初值依赖于其它成员,在类的成员声明中,最好将该成员放在后面。比如在Book类中,成员m_total的初值依赖于另外两个成员:m_number和m_price,所以在成员声明时,最好把m_total放在m_number和m_price的后面;
(2)成员m_name的类型为string,而函数的第一个参数为const char* pc,是一个字符串字面量。在初始化列表中:m_name(const char* pc),相当于用const char* pc去初始化一个string的对象m_name,之所以可行是因为string类定义了一个接收const char*类型参数的non-explicit构造函数,可以将const char*隐式转换为string对象。
string str = "I wana kick your ass"; //将右侧的const char*隐式转换为string的对象。
在构造函数中,如果形参列表只有一个没有默认值参数,也就是只需要提供一个实参即可调用的构造函数,如果不使用explicit修饰的话,将会执行形参类型到类类型的转换,比如Book类有如下的构造函数声明:
Book(const char* pc, unsigned number = 100, double price = 10); //non-explicit构造函数 Book book = "I wanna kick your ass"; //将会把右侧的字符串字面量隐式的转换为Book类的对象 //要阻止这种隐式转换,需要在构造函数的声明前面加上explicit关键字: explicit Book(const char* pc, unsigned number = 100, double price = 10); Book book = "I wanna kick your ass"; //不能隐式转换,编译报错
(3)构造函数-3第一个参数类型为const string&,如果没有const,使用const string对象进行调用将会出错:
const string str("lost");
Book book(str, 10, 100); //error,string&无法绑定到一个const string的对象;
(4)构造函数-2和构造函数-3是一对重载的构造函数,调用顺序与重载函数一致,比如:
Book book1("lost", 10, 100); //第一个实参为字符串字面量,由于字符串字面量的类型就是const char*,与构造函数-2的参数类型精确匹配,那么将调用构造函数-2;
Book book2(string("lost"), 10, 100); //第一个实参为string的对象,与构造函数-3精确匹配,调用构造函数-3;
前面说了,由于string类接收const char*参数的构造函数不是non-explicit的,可以隐式的将字符串字面量转换为string类的对象,所以在Book类中,可以不定义构造函数-2。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)