下面学习下,C++类成员的初始化顺序,例如:
class A
{
private:
int n1;
int n2;
public:
A():n2(0),n1(n2+2){}
void Print(){
cout << "n1:" << n1 << ", n2: " << n2 <<endl;
}
};
int main()
{
A a;
a.Print(); // 输出结果 n1:46 , n2:0
return 0;
}
上面的程序输出时,n1的值是一个随机值,并不是符合预期的值,n2对应的值是0符合预期。下面总结下成员变量的初始化顺序
一般情况下在创建对象时,构造函数由编译器来调用。但是可以主动调用构造函数来创建匿名对象,例如:
class Person{
private:
int age;
public:
Person(int age) {
this->age = age;
}
void Display() const {
cout << "age = " << age << endl;
}
};
void test(const Person &r) {
r.Display();
}
int main()
{
test(Person(20)); // 主动调用构造函数创建匿名对象
return 0;
}
析构函数可以主动调用,但是C++规范一般不推荐这么做,主动调用析构函数可能会导致内存重复释放,例如:
class Person{
private:
char *name;
public:
Person() {
this->name = new char[10];
}
~Person() {
cout << "begin to delete" << endl;
delete [] name;
}
};
int main()
{
Person obj;
obj.~Person();
return 0;
}
输出结果
begin to delete
begin to delete
Process returned 0 (0x0) execution time : 0.006 s
Press any key to continue.
在类里面声明静态成员变量,例如:
class Base
{
public:
Base(int val = 0):m_x(val){
cout << "Base constructor" << endl;
}
public:
static int m_x;
};
类的静态成员变量存放在全局数据区,它的生命周期和普通的静态变量一样,程序运行时进行分配内存和初始化,程序结束时则被释放。静态成员变量属于类,多个类对象之间共享这个静态成员变量。注意:静态成员仍然遵循public,private,protected访问准则
静态数据成员不能在类中初始化,一般在类外(在.cpp文件中,而不是在头文件中)和main()函数之前初始化,缺省时初始化为0。
class Base
{
public:
Base(int val = 0):m_x(val){
cout << "Base constructor" << endl;
}
public:
static int m_x;
};
int Base::m_x = 20;
注意:只有在类中声明静态变量才加static关键字,如果是在类外初始化静态成员变量不能加static关键字,不然会报错。
访问类的静态成员变量一般使用下面两种方法:类名::变量名、类对象.变量名、(指向类对象的指针)->静态成员变量也可以在类的成员函数(静态或非静态成员函数)中直接访问静态成员变量。例如:
class Base
{
public:
static int m_x;
void Display() {
cout << "m_x = " << m_x << endl; // 类的非静态成员函数,直接访问静态成员变量
}
};
int Base::m_x = 20;
int main()
{
Base obj;
Base *p = &obj;
cout << "Base::m_x = " << Base::m_x << endl;
cout << "Base::m_x = " << obj.m_x << endl;
cout << "Base::m_x = " << p->m_x << endl;
obj.Display();
return 0;
}
类的static数据成员,像普通数据成员一样,不能在类的定义体中初始化。相反,static数据成员通常在定义时才初始化。但是,const static数据成员就可以在类的定义体中进行初始化。同样,它可以用在任何需要常量表达式的地方,例如:指定数组成员daily_tbl的大小。
class Account{
private:
//const static常量在类体中进行初始化
const static period = 20;
double daily_tbl[period];
double amount;
public:
static double rate(){
return interestRate;
}
static void setRate(double newRate){
interestRate = newRate;
}
};
下面的代码使用未初始化的静态成员变量,编译时报错,例如:
class Base
{
public:
static int m_x;
static void Display() {
cout << "m_x = " << m_x << endl;
}
};
int main()
{
Base obj;
Base *p = &obj;
cout << "Base::m_x = " << Base::m_x << endl;
cout << "Base::m_x = " << obj.m_x << endl;
cout << "Base::m_x = " << p->m_x << endl;
obj.Display();
return 0;
}
类静态成员函数的声明与静态成员变量的声明类似,例如:
class Base
{
public:
static int m_x;
static void Display() {
cout << "m_x = " << m_x << endl;
}
};
类的静态成员函数与非静态成员函数都可以直接调用静态成员函数,例如:
class Base
{
public:
void Ouput() {
Display(); // 1、非静态成员函数调用静态成员函数
}
static void Show() {
Display(); // 2、静态成员函数调用静态成员函数
}
public:
static int m_x;
static void Display() {
cout << "m_x = " << m_x << endl;
}
};
下面对类的静态成员变量和成员函数作个总结,如下:
静态成员函数不能直接访问类的非静态成员,但是可以通过外部传入对象指针来间接访问类的非静态成员,例如:
class CExample
{
public:
static void Func(CExample *pobj);
private:
int m_age;
};
void CExample::Func(CExample *pobj)
{
CExample *pThis = pobj;
pThis->m_age = 100; // 通过传进来的对象指针,在静态成员函数中间接访问非静态数据成员
}
int main()
{
CExample obj;
CExample::Func(&obj);
return 0;
}
编译器在编译一个普通成员函数时,会隐式地增加一个形参 this,并把当前对象的地址赋值给 this,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数。静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
在函数声明的后面加上const,就定义了一个常量成员函数,例如:
class List{
private:
int length;
int weight;
public:
List(int l, int w){
length = l;
weight = w;
}
int getLength() const; //常成员函数
int getWeight(); //非常成员函数
};
注意:在类外定义常成员函数时,也要加上const关键字
int List::getLength() const{ //类外定义也要加上const关键字
return length;
}
如果在常量成员函数中修改对象的数据成员,编译时会出错
int List::getLength() const{
return length ++;
}
const成员函数存在的意义在于它能被const常对象调用,而且常对象只能调用常成员函数。
const List list1(10, 10); //定义一个常对象
list1.getLength(); //常对象调用常量成员函数
常对象不能调用非常成员函数,不然在编译时会出错
const List list1(10, 10); //定义一个常对象
list1.getWeight(); //常对象调用非常成员函数,在编译时会出错
非常对象可以调用常成员函数
List list1(10, 10); //定义一个非常对象
list1.getLength(); //非常对象调用常成员函数
有些时候,如果必须要让const函数具有修改某个成员数据值的能力。比如一些内部的状态量,对外部用户无所谓,但是对整个对象的运行却大有用处。遇到这种问题,可以把一个成员数据定义为mutable(多变的),它表示这个成员变量可以被const成员函数修改却不违法,例如:
class List{
private:
mutable bool is_valid;
public:
void checkList() const{
if(is_valid){
is_valid = false;
} else {
is_valid = true;
}
}
};