C++ 类的成员函数

C++ 类的成员函数

  • 一、普通成员函数
    • 1、普通成员函数的声明
    • 2、普通成员函数的定义方式
    • 3、普通成员函数的调用方式
  • 二、构造函数
    • 1、构造函数的分类
    • 2、构造函数的特点
    • 3、构造函数的作用
    • 4、构造函数何时调用
    • 5、构造函数的调用方式
    • 6、构造函数的定义方式
    • 7、构造函数初始化成员的方式
    • (一)、自定义构造函数
      • 函数原型
    • (二)、默认构造函数
      • 1、函数原型
      • 2、自定义默认构造函数的方式:
      • 3、默认构造函数的注意事项:
      • 4、默认构造函数的作用
    • (三)、拷贝构造函数
      • 1、拷贝构造函数的类型
      • 2、拷贝构造函数的功能
      • 3、拷贝构造函数何时调用
      • 4、默认拷贝构造函数
      • 5、自定义拷贝构造函数
  • 四、析构函数
    • 1、析构函数分类
    • 2、析构函数的特点
    • 3、析构函数的作用
    • 4、析构函数何时调用
    • 5、默认析构函数
    • 6、自定义析构函数
      • 1)、函数原型
      • 2)、何时需要自定义析构函数
  • 五、赋值运算符重载函数
    • 1、赋值运算符的分类
    • 2、赋值运算符的功能
    • 3、何时使用赋值运算符
    • 4、默认的赋值运算符
    • 5、自定义的赋值运算符
  • 六、移动构造函数(C++11)
    • 1、函数原型
    • 2、移动构造函数的原理
    • 3、何时调用移动构造函数
    • 4、隐式声明的移动构造函数
  • 七、移动构赋值运算符函数(C++11)
    • 1、函数原型
    • 2、何时调用移动构赋值运算符函数
    • 3、移动赋值运算符的作用
    • 4、隐式声明的移动赋值运算符
  • 八、静态成员函数(static成员函数)
    • 1、函数原型
    • 2、静态成员函数的特性
  • 九、常成员函数(const成员函数)
    • 1、函数原型
    • 2、常成员函数的特性
    • 3、常成员函数的作用
  • 十、三五法则
    • 1、三法则
    • 2、五法则
    • 3、三五法则

一、普通成员函数

1、普通成员函数的声明

普通成员函数必须在类中声明,声明方式与声明普通函数的方式一样

class People
{
public:
    void getWeight();
    void getHeight(); //成员函数
};

2、普通成员函数的定义方式

成员函数的定义有两种方式:
1)、在类中定义,此时该函数默认为内联函数;
2)、在类外定义,但要使用作用域限定符(::)

class Rectangle
{
private:
    float height;
    float width; //数据成员
    
public:
    float getWidth()
    {
    	return width; //类中定义,默认为内联函数
	}
	
    float getHeight(); 
};

//类外定义
float Rectangle::getHeight()
{
	return height;
}

3、普通成员函数的调用方式

1)、通过对象使用成员运算符(.)调用;
2)、通过对象指针使用间接成员运算符(->)调用

二、构造函数

1、构造函数的分类

1)、自定义构造函数(可有参数也可以没有参数);
2)、默认构造函数;
3)、拷贝构造函数

当没有自定义构造函数时,编译器会提供一个默认的构造函数;当自定义构造函数后,编译器不再提供默认构造函数

2、构造函数的特点

1)、构造函数没有返回值和声明类型;
2)、构造函数名与类名相同;
3)、构造函数可以有参数也可以没有参数;
4)、构造函数的函数体可以为空也可以不为空;
5)、构造函数可在类中定义也可在类外定义;
6)、构造函数不能声明为const型的;
7)、构造函数的参数可以有默认值;
8)、类可以有多个构造函数

3、构造函数的作用

用来初始化对象的数据成员

4、构造函数何时调用

构造函数在创建对象时自动调用

5、构造函数的调用方式

1)、显式调用
2)、隐式调用

6、构造函数的定义方式

1)、在类中定义;
2)、可在类外定义,此时需要使用作用域限定符(::)

7、构造函数初始化成员的方式

1)、在函数体内一一赋值初始化;
2)、采用初始化列表初始化

赋值初始化示例:

#include 
using namespace std;

class Student
{
private:
    int m_age;
    float m_score;

public:
    Student() { }
    Student(int age, float score)
    {
        m_age = age;
        m_score = score;
    }

    void show()
    {
        cout << "age = " << m_age
             << " score = " << m_score
             << endl;
    }
};

int main()
{
    Student stu = Student(16, 90); //显式调用
    stu.show();

    Student stu1(18, 96);  //隐式调用
    stu1.show();

    return 0;
}

初始化列表示例

#include 
using namespace std;

class Student
{
private:
    int m_age;
    float m_score;

public:
    Student(int age, float score) : m_age(age), m_score(score)
    {

    }

    void show()
    {
        cout << "age = " << m_age
             << " score = " << m_score
             << endl;
    }
};

int main()
{
    Student stu = Student(16, 90); //显式调用
    stu.show();

    Student stu1(18, 96);  //隐式调用
    stu1.show();

    return 0;
}

(一)、自定义构造函数

函数原型

className(argument-list)
{
	... //
}

(二)、默认构造函数

1、函数原型

className()
{

}

编译器提供的默认构造函数不接受任何参数,函数体也不执行任何操作;

2、自定义默认构造函数的方式:

1)、给已有的构造函数的所有参数提供默认值;
2)、通过函数重载定义一个没有参数的构造函数

默认构造函数可在类中定义也可在类外定义,在类外定义时需要使用作用域限定符(::)

例如:

class Rectangle
{
private:
    float height;
    float width; 
    
public:
     Rectangle(float w = 0, float h = 0); //默认构造函数定义方式1
     Rectangle() //默认构造函数定义方式2
     {
     	height = 0;
     	width = 0;
	 }
};

3、默认构造函数的注意事项:

1)、如果在类中没有显式定义构造函数,则C++自动提供默认构造函数;
2)、如果显式定义了构造函数,则C++不会再提供默认构造函数,必须为类提供一个默认构造函数;
3)、默认构造函数只能有一个;
4)、设计类时,通常应提供对所有类成员做隐式初始化的默认构造函数;
5)、默认构造函数初始化对象时对象的初始值是未知的

4、默认构造函数的作用

有了默认构造函数(没有参数或所有参数都有默认值),在创建对象的时候就不必初始化对象

#include 
using namespace std;

class Student
{
private:
    int m_age;
    float m_score;

public:
    Student(){}
    Student(int age, float score)
    {
        m_age = age;
        m_score = score;
    }

    void show()
    {
        cout << "age = " << m_age
             << " score = " << m_score
             << endl;
    }
};

int main()
{
    Student stu; //有了默认构造函数就可以先定义对象再进行初始化,否则就必须在定义的同时进行初始化: Student stu = Student(16, 90);
    stu = Student(16, 90); 
    stu.show();

    return 0;
}

(三)、拷贝构造函数

类 T 的拷贝构造函数是非模板构造函数,其首个形参为 T&、const T&、volatile T& 或 const volatile T&,而且要么没有其他形参,要么剩余形参均有默认值
当没有自定义拷贝构造函数时,编译器会提供一个默认的拷贝构造函数;当自定义拷贝构造函数后,编译器不再提供默认拷贝构造函数

1、拷贝构造函数的类型

拷贝构造函数分为两类:
1)、默认拷贝构造函数
2)、自定义拷贝构造函数

2、拷贝构造函数的功能

逐个复制非静态成员的值

3、拷贝构造函数何时调用

拷贝构造函数在以下三种情况下调用:
1)、用类的一个对象初始化另一个对象;
2)、将对象作为实参传递给一个非引用类型的形参(即按值传递);
3)、函数返回对象

4、默认拷贝构造函数

函数原型

className(const className &);

拷贝构造函数说明:
1)、该函数接收一个指向类类型的引用作为参数;
2)、当没有自定义拷贝构造函数时,编译器会提供一个默认的拷贝构造函数作为其类的非 explicit 的 inline public 成员;当自定义拷贝构造函数后,编译器不再提供默认拷贝构造函数;
3)、默认拷贝函数对成员的复制称之为浅拷贝

5、自定义拷贝构造函数

什么情况下需要自定义拷贝构造函数?

当类中有需要用new来分配存储空间的成员时,需要自定义拷贝构造函数,此时的拷贝称之为深拷贝

#include 
#include 

using namespace std;

class Str
{
public:
    char *p;

public:
    Str(char *s)
    {
        int len = strlen(s);
        p = new char[len + 1];
        strcpy(p, s);
        cout << "调用构造函数" << endl;
    }

    Str(const Str &s)
    {
        int len = strlen(s.p);
        p = new char[len + 1];
        strcpy(p, s.p);
        cout << "调用拷贝构造函数" << endl;
    }

    ~Str()
    {
        if (p != NULL)
        {
            delete [] p;
        }
        cout << "调用析构函数" << endl;
    }
};

int main()
{
    char s[] = "Hello";
    Str s1(s);
    Str s2 = s1;

    cout << s1.p << endl;
    cout << s2.p << endl;

    return 0;
}

四、析构函数

1、析构函数分类

1)、自定义的析构函数;
2)、默认的析构函数
当没有自定义析构函数时,编译器会提供一个默认的析构函数;当自定义析构函数后,编译器不再提供默认拷贝构造函数

2、析构函数的特点

1)、析构函数声明时须在函数名前面加上~;
2)、析构函数没有参数,故不能重载,故类只能有一个析构函数;
3)、析构函数没有返回值和声明类型;
4)、若在类中没有显式定义析构函数,C++会自动提供一个默认的析构函数;若显式定义了析构函数,则不会再提供默认的析构函数;
5)、若某个类作为基类,则一般将析构函数声明为虚析构函数
6)、析构函数可以声明为纯虚,例如对于需要声明为抽象类,但没有其他可声明为纯虚的适合函数的基类;
7)、析构函数可在类中定义也可在类外定义,在类外定义时需要使用作用域限定符(::)

3、析构函数的作用

析构函数用来销毁对象和为对象的数据成员使用new分配的内存空间

4、析构函数何时调用

以下情况下,析构函数是由编译器决定调用
1)、若创建的是静态存储类对象,则析构函数在程序运行结束时被自动调用;
2)、若创建的是自动存储类对象,则析构函数在程序执行完对象所在的代码块时被自动调用;
3)、若对象是通过new创建的,则析构函数在使用delete释放对象占用的内存时被自动调用
4)、若创建的是临时对象,则程序在结束对该对象的使用时调用析构函数

5、默认析构函数

函数原型

~className()
{
}

6、自定义析构函数

1)、函数原型

 ~className() 
{
}

virtual ~className() //类作为基类时的通常设为virtual
{
}

示例:

#include 
using namespace std;

class Student
{
public:
    Student(){}
    ~Student(){cout << "对象被销毁" << endl;}
};

int main()
{
    {
        Student stu;

        Student *sp = new Student;
        delete sp;
    }
    return 0;
}

运行结果:

对象被销毁
对象被销毁

2)、何时需要自定义析构函数

在以下情况下需要自定义的析构函数;
1)、当类中有需要用new来分配存储空间的成员时;
2)、当某个类作为另一个类的基类

五、赋值运算符重载函数

1、赋值运算符的分类

1)、自定义的赋值运算符
2)、编译器提供的默认的赋值运算符

当没有自定义赋值运算符时,编译器会提供一个默认的赋值运算符;当自定义赋值运算符后,编译器不再提供默认赋值运算符

2、赋值运算符的功能

与拷贝构造函数相似,赋值运算符也对非静态成员进行逐个复制,如果成员本身是类对象,则程序将使用为这个类定义的赋值运算符来复制该成员,但静态数据不受影响

3、何时使用赋值运算符

将已有的对象赋给另一个对象时

MyClass foo;
MyClass bar (foo);       // 初始化对象,调用拷贝构造函数
MyClass baz = foo;       // 初始化对象,调用拷贝构造函数

foo = bar;               // bar已经被初始化: 调用赋值运算符

4、默认的赋值运算符

函数原型

className & operator=(const className &);

5、自定义的赋值运算符

什么时候需要自定义赋值运算符

1)、用非类类型的值为类类型的对象赋值时(当然,这种情况下我们可以不提供相应的赋值运算符重载函数,而只提供相应的构造函数);
2)、当类中含有指针成员,将同一个类的一个对象赋值给另一个对象时

六、移动构造函数(C++11)

1、函数原型

className (className &&);

类 T 的移动构造函数是非模板构造函数,其首个形参是 T&&、const T&&、volatile T&& 或 const volatile T&&,且无其他形参,或剩余形参均有默认值

2、移动构造函数的原理

拷贝构造函数中,对于指针要采用深拷贝,而移动构造函数中,对于指针采用浅拷贝。类中有指针时浅拷贝之所以危险,是因为两个指针共同指向同一片内存空间,若将第一个指针释放,则另一个指针的指向就合法了。

3、何时调用移动构造函数

同拷贝构造函数

4、隐式声明的移动构造函数

若不对类提供任何用户定义的移动构造函数,且下列各项均为真:
1)、没有用户声明的复制构造函数;
2)、没有用户声明的复制赋值运算符;
3)、没有用户声明的移动赋值运算符;
4)、没有用户声明的析构函数;
则编译器将声明一个移动构造函数,作为其类的非 explicit 的 inline public 成员

#include 
#include 

using namespace std;

class Str
{
public:
    char *p;

public:
    Str(char *s)
    {
        int len = strlen(s);
        p = new char[len + 1];
        strcpy(p, s);
        cout << "调用构造函数" << endl;
    }

    Str(Str &&s) //移动构造函数
    {
        p = s.p;
        s.p = nullptr;
        cout << "调用移动构造函数" << endl;
    }

    ~Str()
    {
        delete [] p;
        cout << "调用析构函数" << endl;
    }
};

int main()
{
    char s[] = "Hello";
    Str s1(s);
    Str s2(move(s1));
    cout << s2.p << endl;

    return 0;
}

七、移动构赋值运算符函数(C++11)

1、函数原型

className &operator=(className &&);

2、何时调用移动构赋值运算符函数

同赋值运算符

3、移动赋值运算符的作用

执行与移动构造函数相同的工作

4、隐式声明的移动赋值运算符

若不对类类型(struct、class 或 union)提供任何用户定义的移动赋值运算符,且下列各项均为真:
没有用户声明的复制构造函数;
没有用户声明的移动构造函数;
没有用户声明的复制赋值运算符;
没有用户声明的析构函数;
隐式声明的移动赋值运算符不会被定义为弃置,
(C++14 前)
则编译器将声明一个移动赋值运算符,作为其类的 inline public 成员

#include 
#include 

using namespace std;

class Str
{
public:
    char *p;

public:
    Str()
    {

    }

    Str(char *s)
    {
        int len = strlen(s);
        p = new char[len + 1];
        strcpy(p, s);
        cout << "调用构造函数" << endl;
    }

    Str(Str &&s)
    {
        p = s.p;
        s.p = nullptr;
        cout << "调用移动构造函数" << endl;
    }

    Str &operator=(Str &&s)
    {
        p = s.p;
        s.p = nullptr;
        cout << "调用移动赋值运算符" << endl;
    }

    ~Str()
    {
        delete [] p;
        cout << "调用析构函数" << endl;
    }
};

int main()
{
    char s[] = "Hello";
    Str s1(s);
    Str s3;
    Str s2(move(s1));

    s3 = move(s2);
    cout << s3.p << endl;

    return 0;
}

八、静态成员函数(static成员函数)

1、函数原型

static type functionName(arguments-list)
{
	... //函数体
}

2、静态成员函数的特性

1)、静态成员遵循类成员访问规则(私有、保护、公开);
2)、静态成员函数不关联到任何对象,即使不定义类的任何对象它们也存在,它们无 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数);
3)、静态成员函数不能为 virtual、const 或 volatile;
4)、静态成员函数的地址可以存储在常规的函数指针中,但不能存储于成员函数指针中;
5)、静态数据成员不能为 mutable;
6)、在命名空间作用域中,若类自身具有外部连接(即不是无名命名空间的成员),则类的静态数据成员也具有外部连接。局部类(定义于函数内部的类)和无名类,包括无名类的成员类,不能拥有静态数据成员;
7)、静态成员函数在类外定义时不需要加关键字static

#include 
using namespace std;

class Student
{
public:
    Student(char *name, int age, float score);
    void show();

public:  //声明静态成员函数
    static int getTotal();
    static float getPoints();

private:
    static int m_total;  //总人数
    static float m_points;  //总成绩

private:
    char *m_name;
    int m_age;
    float m_score;
};

int Student::m_total = 0;
float Student::m_points = 0.0;

Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score)
{
    m_total++;
    m_points += score;
}
void Student::show(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}

//定义静态成员函数
int Student::getTotal(){
    return m_total;
}

float Student::getPoints(){
    return m_points;
}

int main()
{
    (new Student("小明", 15, 90.6)) -> show();
    (new Student("李磊", 16, 80.5)) -> show();
    (new Student("张华", 16, 99.0)) -> show();
    (new Student("王康", 14, 60.8)) -> show();

    int total = Student::getTotal();
    float points = Student::getPoints();
    cout<< "当前共有" << total
        << "名学生,总成绩是" << points
        << ",平均分是" << points / total << endl;

    return 0;
}

运行结果:

小明的年龄是15,成绩是90.6
李磊的年龄是16,成绩是80.5
张华的年龄是16,成绩是99
王康的年龄是14,成绩是60.8
当前共有4名学生,总成绩是330.9,平均分是82.725

九、常成员函数(const成员函数)

1、函数原型

type functionName(arguments-list) const;

2、常成员函数的特性

1)、常成员函数需要在声明和定义的时候在函数头部的结尾加上 const 关键字
2)、常对象只能调用常成员函数,普通对象可以调用所有成员函数;
3)、常成员函数中的const修饰的是该成员函数隐含的this指针,常成员函数中不能改变数据成员的值
4)、const成员函数既可使用const数据,也可使用非const数据,但都不能改变值

#include 
#include 

using namespace std;

class Test
{
private:
    int m_a;
    int m_b;

public:
    Test(int a, int b) : m_a(a), m_b(b)
    {

    }

    int getA() const
    {
        return m_a;
    }

    int getB() const
    {
        return m_b;
    }
};

int main()
{
    Test test(1, 2);

    int a = test.getA();
    int b = test.getB();

    cout << "a = " << a << endl;
    cout << "b = " << b << endl;

    return 0;
}

3、常成员函数的作用

常成员函数中不得修改类中的任何数据成员的值

例如:

class Complex
{
public:
    Complex();
    Complex(int real, int image);

    virtual ~Complex();
	
	//编译出错,因为修改了数据成员,去掉const就正确
    void Print() const
    {
        cout << "real = " << ++m_real << " image = " << ++m_image << endl;
    }

	//正确
    void Print() const
    {
        cout << "real = " << m_real << " image = " << m_image << endl;
    }

private:
    int m_real;
    int m_image;
};

十、三五法则

1、三法则

如果需要析构函数,则一定需要拷贝构造函数和拷贝赋值操作符

2、五法则

在 C++11 标准中,为了支持移动语义,又增加了移动构造函数和移动赋值运算符,这样共有五个特殊的成员函数,所以又称为“C++五法则”;

3、三五法则

三法则”是针对较旧的 C++89 标准说的,“五法则”是针对较新的 C++11 标准说的;为了统一称呼,后来人们干把它叫做“C++ 三/五法则”

1). 需要析构函数的类也需要拷贝构造函数和拷贝赋值函数;
2). 需要拷贝操作的类也需要赋值操作,反之亦然;
3). 析构函数不能是删除的;
4). 如果一个类有删除的或不可访问的析构函数,那么其默认和拷贝构造函数会被定义为删除的;
5). 如果一个类有const或引用成员,则不能使用合成的拷贝赋值操作

注:如果在类中没有显式定义以下函数,则编译器自动提供

1)、构造函数
2)、析构函数
3)、拷贝构造函数
4)、赋值运算符
5)、地址运算符

参考:

1、《C++ Primer Plus》
2、《Primer C++》
3、https://zh.cppreference.com
4、http://c.biancheng.net/view/2230.html
5、C++ 三五法则

你可能感兴趣的:(C++)