C++ 类使用

一、C++ 类使用

1、类成员的初始化顺序

下面学习下,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符合预期。下面总结下成员变量的初始化顺序

  • 成员变量使用初始化列表初始化时,成员变量的初始化顺序与初始化列表的顺序无关,只与定义成员变量的顺序有关。因为成员变量的初始化次序是根据变量在内存中次序进行初始化,而内存中的排列顺序早在编译期就根据变量的定义次序决定了。
  • 如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
  • 类中const成员常量必须在构造函数初始化列表中初始化。
  • 类中引用类型必须在构造函数初始化列表中初始化。
  • 类中static成员变量,必须在类外初始化。

2、构造函数与析构函数主动调用

2.1、构造函数主动调用

一般情况下在创建对象时,构造函数由编译器来调用。但是可以主动调用构造函数来创建匿名对象,例如:

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;
}

2.2、主动调用析构函数

析构函数可以主动调用,但是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.

3、类的静态成员变量

3.1、声明类的静态成员变量

在类里面声明静态成员变量,例如:

class Base
{
public:
    Base(int val = 0):m_x(val){
        cout << "Base constructor" << endl;
    }
    
public:
    static int m_x;
};

类的静态成员变量存放在全局数据区,它的生命周期和普通的静态变量一样,程序运行时进行分配内存和初始化,程序结束时则被释放。静态成员变量属于类,多个类对象之间共享这个静态成员变量。注意:静态成员仍然遵循public,private,protected访问准则

3.2、类静态成员变量初始化

静态数据成员不能在类中初始化,一般在类外(在.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关键字,不然会报错。

3.3、访问类静态成员变量

访问类的静态成员变量一般使用下面两种方法:类名::变量名、类对象.变量名、(指向类对象的指针)->静态成员变量也可以在类的成员函数(静态或非静态成员函数)中直接访问静态成员变量。例如:

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;
}

3.4、类const static成员变量

类的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;
}

4、类的静态成员函数

4.1、声明类的静态成员函数

类静态成员函数的声明与静态成员变量的声明类似,例如:

class Base
{
public:
    static int m_x;

    static void Display() {
        cout << "m_x = " << m_x << endl;
    }
};

4.2、调用类的静态成员函数

类的静态成员函数与非静态成员函数都可以直接调用静态成员函数,例如:

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;
}

4.3、静态成员函数与非静态成员函数的区别

编译器在编译一个普通成员函数时,会隐式地增加一个形参 this,并把当前对象的地址赋值给 this,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数。静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。

5、const类成员函数

5.1、常成员函数的定义

在函数声明的后面加上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 ++;
}

5.2、常成员函数的作用

const成员函数存在的意义在于它能被const常对象调用,而且常对象只能调用常成员函数。

const List list1(10, 10);   //定义一个常对象
list1.getLength();          //常对象调用常量成员函数

常对象不能调用非常成员函数,不然在编译时会出错

const List list1(10, 10);   //定义一个常对象  
list1.getWeight();          //常对象调用非常成员函数,在编译时会出错

非常对象可以调用常成员函数

List list1(10, 10);  //定义一个非常对象
list1.getLength();   //非常对象调用常成员函数

5.3、如何让一个const成员函数具有修改对象数据成员的能力

有些时候,如果必须要让const函数具有修改某个成员数据值的能力。比如一些内部的状态量,对外部用户无所谓,但是对整个对象的运行却大有用处。遇到这种问题,可以把一个成员数据定义为mutable(多变的),它表示这个成员变量可以被const成员函数修改却不违法,例如:

class List{
private:
     mutable bool is_valid;
public:
    void checkList() const{
        if(is_valid){
            is_valid = false;
        } else {
            is_valid = true;
        }
    }
};

你可能感兴趣的:(C++,c++,开发语言,后端)