1. 函数形参默认值
(1) 建议函数(不仅仅是构造函数)形参默认值只在函数声明中指定;
(函数声明和定义写在同一个文件中,则函数声明、定义两者之一或两者都可指定形参默认值,两者都指定默认值时,默认值需相同;但函数声明写在头文件、定义写在源文件时,只能在函数声明中指定形参默认值。)
(2)函数参数默认值 “一默到底”;
(3)若在调用函数时传入参数,则相应参数会替换掉默认值;
(4)构造函数只能通过初始化列表对const成员变量进行初始化,不可在其函数体中使用赋值语句进行初始化;
(5)注意:C语言不支持函数形参有默认值的特性。
2. 隐式类型转换和explicit
2.1 构造函数隐式类型转换
class Animal {
public:Animal() { // 无参构造函数cout << "Animal()" << endl;}Animal(int age) :age_(age) { // 有参构造函数1cout << "Animal(int age)" << endl;}Animal(int age, char gender) :age_(age), gender_(gender) { // 有参构造函数2cout << "Animal(int age, char gender)" << endl;}
private:int age_;char gender_;
};void test() {Animal animal0; // 无参构造函数Animal animal1 = 10; // 有参构造函数1Animal animal2 = { 20,'n' }; // 有参构造函数2Animal animal3 = (30, 'n'); // 有参构造函数1,写法错误!!!
}
运行结果:
如上示例,animal1和animal2的构造发生了隐式类型转换。
注意:Animal animal3 = (30, 'n')这种写法错误,因为逗号表达式的值为其中最后一个表达式的值,这里发生窄化转换(不同数值类型的转换),相当于使用n来初始化age,所以调用构造函数Animal(int age)。
2.2 explicit关键字
如何禁止调用构造函数时的隐式类型转换?使用explicit关键字修饰构造函数即可。
使用explicit修饰后,需要显式地指定初始化的类型。
如:
class Animal {
public:explicit Animal(int age) :age_(age) { cout << "Animal(int age)" << endl;}
private:int age_;char gender_;
};void test() {Animal animal1 = 10; // 错误,禁止隐式类型转换。Animal animal2 = static_cast<Animal>(10); // 显式地指定初始化的类型Animal animal3 = Animal(10); // 显式地指定初始化的类型
}
建议:
(1)单参数的构造函数声明为explicit;
(2)explicit只在函数声明时添加。
3. 常量成员函数
3.1 const修饰成员函数体
则该成员函数为“常量成员函数”,不可修改对象中的成员变量。
示例:
class Student {
public:Student(int id) :id_(id) {}auto getId() const {id_ += 3; // 错误。不可修改成员return id_;}private:int id_;
};
3.2 const修饰返回值为引用的成员函数
则返回的引用不能被修改。
示例:
class Student {
public:Student(int id) :id_(id) {}const auto& getId() const {// id_ += 3; 错误。不可修改成员return id_;}private:int id_;
};void test01() {Student s0(16);// s0.getId() += 3; 错误。不可修改返回的引用cout << s0.getId() << endl;
}
注意:该返回值为引用的成员函数被两个const修饰,
第一个const表示返回的引用不能被修改,第二个const表示函数体中不能修改对象成员。
3.3 const对象只能调用const成员函数,非const对象可调用const成员函数
示例:
class Student {
public:Student(int id, string name) :id_(id),name_(name) {}int getId() const { // const成员函数return id_;}string getName() { // 非const成员函数return name_;}private:int id_;string name_;
};void test01() {const Student s0(16,"ABC"); // const对象cout << s0.getId() << endl; // 正确。const对象可调用const成员函数cout << s0.getName() << endl; // 错误。const对象不可调用非const成员函数Student s1(20, "XYZ"); // 非const对象cout << s1.getId() << endl; // 正确。非const对象可调用const成员函数cout << s1.getName() << endl; // 正确。非const对象可调用非const成员函数
}
(1)非const对象可调用const、非const成员函数;
(2)const对象只可调用const成员函数;
(3)const成员函数可被const、非const对象调用;
(4)非const成员函数只可被非const对象调用。
注意:普通函数(非类成员函数)不能用const修饰!
如下错误!
void func() const { // 错误。普通函数不能用const修饰
}
4. mutable
则const成员函数可修改mutable修饰的成员变量。
class Student {
public:Student(int id, string name) :id_(id),name_(name) {}void setId(int id) const { // 可修改mutable成员id_ = id;}private:mutable int id_;string name_;
};
5. this指针
(1)普通成员函数隐藏一个this指针,该指针指向调用函数的对象;
(2)this指针只能在普通成员函数中使用,全局函数、static函数不能使用this指针;
(3)非const成员函数,this指针是一个指向非const对象的指针常量;
(意思是:非const对象只可调用非const成员函数;并且this指针的指向不能改变)
6. static成员
(1)static全局变量:保存在静态区,作用域为当前文件,无法在其他文件使用(使用extern声明也无法使用);
(2)static成员函数只能访问static成员变量;
(3)static成员变量在类内定义,类外初始化;
类外初始化时不必加static关键字。
如下:
class Student {
private:int id_; static int a_; // 类内定义static成员变量static void setA(); // 类内定义static成员函数
}/* 类外初始化static成员变量、函数 */
int Student::a = 10; // 初始化时不必加static关键字;不给初值则为0.
void Student::setA() {a_ = 99; // 正确,static成员函数可访问static成员变量// id_ = 88; // 错误,static成员函数不可访问非static成员变量
}
(4)static成员变量或函数属于类,为该类所有对象共享;
访问方式:
class Student {
public :static int a;static void setA();
};int Student::a = 10;
void Student::setA() {a = 99;
}void test02() {Student s0(20, "AAA");Student::a; // 通过类访问static成员变量s0.a; // 通过对象访问static成员变量Student::setA(); // 通过类访问static成员函数s0.setA(); // 通过对象访问static成员函数
}
7. 拷贝构造函数
用一个已存在的对象初始化新对象,会调用拷贝构造函数。
(1)拷贝构造函数的第一个形参必须是该类类型的引用,可以没有其他参数;若有其他参数,则其他参数必须有默认值。如:
Student(const Student& s, int age = 18);
(2)拷贝构造函数不建议使用explicit修饰;
8. 隐藏
概念:
派生类中有与基类同名(参数列表可不同)的成员函数,则基类中的该同名成员函数在派生类中不可见(不可调用)。
实例1:派生类中无与基类同名的成员函数。
class Person {
public:void run() {cout << "Person::run()" << endl;}void run(int a) {cout << "Person::run(int a)" << endl;}
};class Student :public Person {
};void test() {Student s;s.run(); // 正常调用s.run(3); // 正常调用
}
示例2:派生类中有与基类同名的成员函数,发生隐藏。
class Person {
public:void run() {cout << "Person::run()" << endl;}void run(int a) {cout << "Person::run(int a)" << endl;}
};class Student :public Person {
public:void run(string s) {cout << "Student::run(string s)" << endl;}
};void test() {Student s;s.run("ABC"); // 正常调用s.run(); // 不可调用基类的run()s.run(3); // 不可调用基类的run(int)
}
发生隐藏时,如何调用基类同名的成员函数?
方式1:在派生类的同名成员函数中调用基类的同名成员函数。(遵循访问权限规则)
class Student :public Person {
public:void run() {Person::run();}
};
方式2:C++ 11中using关键字可让基类同名成员函数在派生类中可见。(让基类的同名成员函数在派生类中以重载的方式使用,遵循访问权限规则)
注意: 使用using时只能指定函数名,无需参数列表。
class Person {
public:void run() {cout << "Person::run()" << endl;}void run(int a) {cout << "Person::run(int a)" << endl;}
};class Student :public Person {
public:void run(string s) {cout << "Student::run(string s)" << endl;}public:using Person::run; // 只能指定函数名
};void test() {Student s;s.run(); // 可调用基类的run()s.run(13); // 可调用基类的run(int)
}
9. 虚函数相关
9.1 override 修饰派生类中的虚函数
建议在派生类虚函数后加关键字override,避免虚函数名、返回值类型、参数列表写错。
/*派生类中的虚函数*/
virtual void func() override {cout << "Derive::func()" << endl;
}
9.2 final 修饰基类中的虚函数
final修饰基类中的虚函数,禁止在派生类中重写。
/*基类中的虚函数*/
virtual void func() final {cout << "Base::func()" << endl;
}
9.3 建议将基类的析构函数写成虚函数
待补充。。