三、类和对象进阶

2020-10-24  本文已影响0人  silasjs

构造函数

变量 初始化
全局变量 如果程序员在声明变量时没有进行初始化,
则系统自动为其初始化为0。
这个工作在程序启动时完成。
局部变量 系统不进行自动初始化,
所以它的处置需要靠程序员给定。
如果程序员没有设定,
则是一个随机值。

构造函数的定义

定义一个类时,需要为类定义相应的构造函数。构造函数的函数名与类名相同,没有****返回值****。一个类的构造函数可以有多个,即****构造函数允许重载。同一个类的多个构造函数的参数表一定不能完全相同。

构造函数的声明格式如下:

类名(形参1, 形参2, ..., 形参n);

假设类的成员变量有三个,则在类体外定义构造函数时通常有如下3中形式:

//方式一
//使用带入的参数值通过初始化列表为各成员变量赋初值
Student::Student(int sno, int age, string name):_sno(sno), _age(age), _name(name) {
    
}
//或者
//使用固定值在初始化列表中为个成员变量赋初值
Student::Student():_sno(11111), _age(13), _name("slh") {

}

//方式二
Student::Student(int sno, int age, string name) {
    _sno = sno;
    _age = age;
    _name = name;
}

//方式三
Student::Student() {
    _sno = 12345;
    _age = 12;
    _name = "tyg";
}

再比如类Student已经声明了下列4个构造函数:

class Student {
public:
    Student();
    Student(int);
    Student(int, int);
    Student(int, int, string);
    void printStudent();
private:
    int _sno;
    int _age;
    string _name;
};

在类体外定义构造函数

Student::Student():_sno(12321), _age(23), _name("df") {
    
}
Student::Student(int sno):_age(12), _name("dfs") {
    _sno = sno;
}
Student::Student(int sno, int age):_name("dsf") {
    _sno = sno;
    _age = age;
}
Student::Student(int sno, int age, string name) {
    _sno = sno;
    _age = age;
    _name = name;
}

构造函数的使用

C++语言规定,创建类的任何对象时都一定会调动构造函数进行初始化。对象需要占据内存空间,生成对象时,为对象分配的这段内存空间的初始化由构造函数完成。

特别地,如果程序中声明了对象数组,即数组的每个元素都是一个对象,则一定要为对象所属的这个类定义一个无参的构造函数。因为数组中每个元素都需要调用无参的构造函数进行初始化,所以必须要有一个不带参数的构造函数。

复制构造函数与类型转换构造函数

复制构造函数是构造函数的一种,也称为拷贝构造函数它的作用是使用一个已存在的对象去初始化另一个正在创建的对象。例如,类对象间的赋值是由复制构造函数实现的。

复制构造函数只有一个参数参数类型是****本类的引用复制构造函数的参数可以是const引用,也可以是非const引用。一个类中可以写两个复制构造函数,一个函数的参数是const引用,另一个函数的参数是非const引用。这样,当调用复制构造函数时,既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数去初始化其他对象。对于类A而言,复制构造函数的原型如下:

//格式一
A::A(const A &)
//格式二
A::A(A &)

例如:

//复制构造函数
Student::Student(Student &s) {
    _sno = s._sno;
    _age = s._age;
    _name = s._name;
}
Student::Student(const Student &s) {
    _sno = s._sno;
    _age = s._age;
    _name = s._name;
}

自动调用复制构造函数的情况有以下3种:

  1. 当用一个对象去初始化本类的另一个对象时,会调用复制构造函数。例如,使用下列形式的说明语句时,即会调用复制构造函数。
    1. 类名 对象名2(对象名1);
    2. 类名 对象名2 = 对象名1;
  2. 如果函数F的参数是类A的对象,那么当调用F时,会调用类A的复制构造函数。换句话说,作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。
  3. 如果函数的返回值是类A的对象,那么当函数返回时,会掉会用类A的复制构造函数。也就是说,作为函数返回值的对象是用复制构造函数初始化的,而调用复制构造函数时的实参,就是return语句所返回的对象。

注意
在复制构造函数的参数表中,加上const是更好的做法。这样复制构造函数才能接收常量对象作为参数,即才能以常量对象作为参数去初始化别的对象。

析构函数

Student::~Student() {
    cout << "student对象被释放了" << endl;
}

类的静态成员

注意
在类体外为静态成员变量赋初值时,前面不能加static关键字,以免和一般的静态变量想混淆。在类体外定义成员函数时,前面也不能加static关键字。

访问静态成员时,成员前面既可以用类名作前缀,也可以使用对象名或对象指针作前缀。这与访问类成员时仅能使用对象名或对象指针作前缀是不同的。

//访问类静态成员的一般格式如下:
类名::静态成员名
//或者
对象名.静态成员名
//或者
对象指针->静态成员名

类的静态成员函数没有this指针不能在静态成员函数内访问非静态的成员,即通常情况下,类的静态成员函数只能处理类的静态成员变量。惊天成员函数内也不能调用非静态成员函数。

变量及对象的生存期和作用域

变量的生存期和作用域

类对象的生存期和作用域

类的对象在生成时调用构造函数,在消亡时调用析构函数,在这两个函数调用之间即是对象的生存期。

常量成员和常引用成员

例如:

class CDemo {
public:
    void setValue(){};//非常量成员函数
}

int main() {
    const CDemo obj;//obj是常量对象
    obj.setValue();//❌
    
    return 0;
}

成员对象和封闭类

一个类的成员变量如果是另一个类的对象,则该成员变量称为成员对象。这两个类为包含关系。包含成员对象的类焦作封闭类

封闭类构造函数的初始化列表

当生成封闭类的对象并进行初始化时,它包含的成员对象也需要被初始化,需要调用成员对象的构造函数。在定义封闭类的构造函数时,需要添加初始化列表,指明要调用成员对象的哪个构造函数。在封闭类构造函数中添加初始化列表的格式如下:

封闭类名::构造函数名(参数表):成员变量1(参数表), 成员变量2(参数表), ... { ... }

初始化列表中的成员变量既可以是成员对象,也可以是基本数据类型的成员变量。对于成员对象,初始化列表的参数表中列出的是成员对象构造函数的参数(它指明了该成员对象如何初始化)

先调用成员对象的构造函数,再调用封闭类对象的构造函数。

封闭类的复制构造函数

如果封闭类的对象是用默认复制构造函数初始化的,那么它包含的成员对象也会用复制构造函数初始化。

友元

友元实际上并不是面向对象的特征,而是为了兼顾C语言程序设计的习惯与C++信息隐藏的特点,而特意增加的功能。友元的概念破坏了类的封装性和信息隐藏,但有助于数据共享,****能够提高程序执行的效率。这是一种类成员的访问权限。

友元使用关键字friend标识。在类定义中,当friend出现在函数说明语句的前面时,表示该函数为类的友元函数。一个函数可以同时说明为多个类的友元函数,一个类中也可以有多个友元函数。当friend出现在类名之前时,表示该类为类的友元类。

友元函数

在定义一个类的时候,可以把一些函数(包括全局函数和其他类的成员函数)声明为友元,这样那些函数就称为本类的友元函数。在友元函数内部可以直接访问本类对象的私有成员。在类定义中,将一个全局函数声明为本类友元函数的格式如下:

friend 返回值类型 类名::类的成员函数名(参数表);

不能把其他类的私有成员函数声明为友元函数。

友元函数不是类的成员函数但允许访问类中的所有成员。在函数体中访问对象成员时,必须使用对象名.对象成员名的方式。

友元函数不受类中的访问权限关键字限制,可以把它放在类的公有、私有、保护部分,结果是一样的。

//Test.hpp
#include <stdio.h>

class Pixel;

class Test {
public:
    void printP(Pixel p);
    void printP(Pixel *p);
};
//Test.cpp
#include "Test.hpp"
#include "Pixel.hpp"

using namespace std;

void Test::printP(Pixel p) {
    cout << "test:x = " << p.x << ", y = " << p.y << endl;
}
void Test::printP(Pixel *p) {
    //❌写法
    //'x' is a private member of 'Pixel'
    //'y' is a private member of 'Pixel'
    cout << "x = " << p->x << ", y = " << p->y << endl;
}
//Pixel.hpp
#include <stdio.h>
#include <iostream>
#include "Test.hpp"

using namespace std;

class Pixel {
private:
    int x, y;
public:
    Pixel(int x0, int y0) {
        x = x0;
        y = y0;
    }
    void printXY() {
        cout << "x = " << x << ", y = " << y << endl;
    }
    friend double getDist(Pixel p1, Pixel p2);
    friend void Test::printP(Pixel p);
};
//main.cpp
#include <iostream>
#include <cmath>
#include "Test.hpp"
#include "Pixel.hpp"

using namespace std;

double getDist(Pixel p1, Pixel p2) {
    double xDist = double(p1.x - p2.x);
    double yDist = double(p1.y - p2.y);
    return sqrt(xDist * xDist + yDist * yDist);
}

int main(int argc, const char * argv[]) {
    
    Pixel p1(0, 0), p2(10, 10);
    p1.printXY();//x = 0, y = 0
    p2.printXY();//x = 10, y = 10
    cout << "p1和p2的间距 = " << getDist(p1, p2) << endl;
    //p1和p2的间距 = 14.1421
    
    Test t;
    cout << "从友元函数中输出:" << endl;
    t.printP(p1);//test:x = 0, y = 0
    t.printP(p2);//test:x = 10, y = 10
    
    return 0;
}

友元类

如果将一个类B说明为另一个类A的****友元类则类B中所有函数都是类A的友元函数,在类B的所有成员函数中都可以访问类A中的所有成员。在类定义中声明友元类的格式如下:

friend class 类名;

友元类的关系是****单向****的。若说明类B是类A的友元类,不等于类A也是类B的友元类。友元类的关系不能传递,即若类B是类A的友元类,而类C是类B的友元类,不等于类C是类A的友元类。

除非确有必要,一般不把整个类说明为友元类,而仅把类中的某些成员函数说明为友元函数。

this指针

上一篇 下一篇

猜你喜欢

热点阅读