C++中的指针类型与构造函数,析构函数

1. 指针类型的作用

1.1 指针取出字节

  • 任何类型的指针占用的空间大小都是相同的
    (32位CPU是4字节;64位CPU是8字节)

  • 既然任何类型的指针占用的空间大小都是相同的,为什么指针还需要类型呢?

  • 指针只是指向了一个内存地址,但是当存内存中取值的时候,系统不知道你要从当前指针指向的地址,取几个字节,指定了指针的类型后,系统就知道取几个字节了。

  • char类型取1个字节,short类型取2个字节,int类型去4个字节。

注意:

  1. bit 是计算机最小的单位; 一个字节 = 8 bit;

  2. 一个十六进制符 需要四位才能表示出来;
    0xf: 十六进制中的 一个16进制数, 比如 f , 需要用四位 二进制才能表示处理;
    十六进制数 f f f : 需要四位表示出来: 1111;

所以一个十六进制符占用的是 半个字节,
两个十六进制符 才是占用的一个字节;

* char字符存储空间为一个字节,
 * 16进制的每个字符需要用4位二进制位来表示,
 * 0x0为0000,0xf为1111,即1个16进制数为4位,
 * 如42 4D 38 04 04 00 00 00 00 00 36 04 00 00,每两个16进制数隔开,
 * 用意是:因为1个16进制数为4位,两个就是8位,
所以两个16进制数 占用8位, 占用了一个

 * 即1个字节,所以这里是14字节,,以字节为单位,容易计数

int main() {
       int i = 123456789;//i的值用16进制表示就是:0x075bcd15


        int *p = &i;
        printf("int:%d\n", *p);//打印出123456789


        //char *c = (char*)&i;
        char *c;
        c = (char*)&i;

        //打印出21,因为是char类型的指针,所以取出1个字节,也就是去的是16进制的15,十进制就是21.
        printf("char:%d\n", *c);

        //打印出-13035,因为是short类型的指针,所以取出2个字节也就是去的是16进制的cd15,十进制就是-13035
        short *s = (short*)&i;
        printf("short:%d\n", *s);

}

C++中的指针类型与构造函数,析构函数_第1张图片

不同的指针类型,可以指示编译器怎样解释特定地址上内存的内容以及该内存区域应该跨越多少内存单元。

如果一个int 型的指针:
寻址到1000 内存处,
那么在32 位机器上跨越的地址空间是1000~1003;

如果一个double 型的指针:
寻址到1000 内存处,
那么在32 位机器上跨越的地址空间是1000~1007;

1.2 指针的定义

指针p也是对象,它同样有地址&p和存储的值p,
只不过,p存储的数据类型是数据的地址。

指针在定义的时候:
声明该变量, 是指针类型的变量:

  int i = 3;
  int* p = &i

但是在使用的时候, 称作是 解地址符: 意思是取出该指针中所保存的地址1, 找到该地址1, 取出地址1中的值;

 int value_i = *p 
 cout<< value_i<< endl;

2. 构造函数

构造函数是指一个特殊的公共成员函数, 它的作用是在创建类的对象时会自动调用,从而用于构造类的对象;

构造函数的特点:

  1. 是指它的函数名称 与所属类的名称相同, 从而编译器知道这是类中一个特殊的成员函数;

  2. 构造函数不允许有返回类型, 除此之外, 构造函数和其他类的成员函数相似;

  3. 有个特点, 通常类名称首字母大写, 所以构造函数的首字母也会大写;

从而函数名称与类名称是否相同, 函数名称首字母是否大写, 是否有无函数返回类型, 这三点,可以区分普通成员函数和构造函数;

所以,我们程序员在创建类的时候, 我们应该自己定义出构造函数;
在自己定义了构造函数后,
好处是,在初始化类的对象时, 可以直接对类中的成员变量赋值;

若没有自定义构造函数,调用系统自动生成的构造函数时, 在初始化对象时,不可以直接对类中的成员变量赋值;

如果程序员没有编写构造函数,则 C++ 会自动提供一个,这个自动提供的构造函数永远不会有人看到它,但是每当程序定义一个对象时,它会在后台静默运行。

构造函数的类型:

  1. 有形参的普通自定义构造函数;
  2. 没有形参的 的默认构造函数;
  3. 有形参的默认构造函数, 但是形参都设置了默认指, 无实参可以调用;

注意,一般构造函数可以有多种参数形式,
一个类可以有多个普通构造函数,前提是参数的个数或者类型不同(C++的函数重载机制)

但是, 默认构造函数只能有且只有一个

程序员通常使用构造函数来初始化对象的成员变量。但实际上,它可以做任何正常函数可以做的事情。

举个例来说明有 无自定义构造函数区别:
单链表中的构造函数:

struct ListNode{
int val;

ListNode *next;
// 节点的构造函数
ListNode (int x): val(x), next(NULL){}
};

因为上述结构体中, 自己定义了 构造函数:

所以在初始化的时候, 可以直接给变量赋值;

ListNode* head = new ListNode(5);

而如果没有自己定义构造函数, 则c++ 默认生成一个构造函数,
但是 使用默认生成的构造函数时, 在初始化的时候,不可以给变量赋值;

2.1 在类中定义构造函数

下面的程序包括一个名为 Demo 的类,其构造函数除了打印消息之外什么都不做。编写它的目的就是为了演示构造函数何时执行。因为 Demo 对象是在两个 cout 语句之间创建的,所以构造函数将在这两个语句生成的输出行之间打印它的消息。

// This program demonstrates when a constructor executes.
#include 
using namespace std;
class Demo
{
    public:
        Demo()
        {
            cout << "Now the constructor is running.\n";
        }
};
int main()
{
    cout << "This is displayed before the object is created. \n";
    Demo demoObj;    // Define a Demo object
    cout << "This is displayed after the object is created.\n";
    return 0;
}

程序中,将构造函数定义为类声明中的内联函数;

程序输出结果为:
This is displayed before the object is created.
Now the constructor is running.
This is displayed after the object is created.

2.2 在类外定义构造函数

当然,像任何其他类成员函数一样,也可以将其原型放在类声明中,然后将其定义在类之外。

在这种情况下,需要添加:

  1. 函数所属类的名称和
  2. 函数名前面的作用域解析运算符。

但是由于构造函数的名称与类名相同,所以名称会出现两次。

Demo:: Demo ()    // 构造函数
{
    cout << "Now the constructor is running. \n";
}

2.3 重载构造函数

我们知道,当两个或多个函数共享相同的名称时,函数名称被称为重载。只要其形参列表不同,C++ 程序中可能存在具有相同名称的多个函数。

任何类成员函数都可能被重载,包括构造函数。例如,某个构造函数可能需要一个整数实参,而另一个构造函数则需要一个 double,甚至可能会有第三个构造函数使用两个整数。只要每个构造函数具有不同的形参列表,则编译器就可以将它们分开。

下面的程序声明并使用一个名为 Sale 的类,它有两个构造函数。第一个构造函数的形参接受销售税率;第二个构造函数是免税销售,没有形参。它将税率设置为 0。这样一个没有形参的构造函数称为默认构造函数。

#include 
#include 
using namespace std;
// Sale class declaration
class Sale
{
    private:
        double taxRate;
    public:
        Sale(double rate) // Constructor with 1 parameter
        {
            taxRate = rate; // handles taxable sales
        }
        Sale ()    // Default constructor
        {
            taxRate = 0.0    // handles tax-exempt sales
        }
        double calcSaleTotal(double cost)
        {
            double total = cost + cost*taxRate;
            return total;
        }
};
int main()
{
    Sale cashier1(.06); // Define a Sale object with 6% sales tax
    Sale cashier2;    // Define a tax-exempt Sale object
   
    // Format the output
    cout << fixed << showpoint << setprecision (2);
    
    // Get and display the total sale price for two $24.95 sales
    cout << "With a 0.06 sales tax rate, the total of the $24.95 sale is $ \n";
    cout << cashier1.calcSaleTotal(24.95) << endl;
    
    
    cout << "\n On a tax-exempt purchase, the total of the $24.95 sale is, of course, $\n";
    cout << cashier2.calcSaleTotal(24.95) << endl;
    return 0;
}

输出结果:

With a 0.06 sales tax rate, the total of the $24.95 sale is $26.45

On a tax-exempt purchase, the totalof the $24.95 sale is, of course, $24.95

注意看此程序如何定义的两个 Sale 对象:

Sale cashier1(.06);
Sale cashier2;

在 cashier1 的名称后面有一对括号,用于保存值,发送给有一个形参的构造函数。但是,在 Cashier2 的名称后面就没有括号,它不发送任何参数。在 C++ 中,当使用默认构造函数定义对象时,不用传递实参,所以不能有任何括号,即:

Sale cashier2 () ;    // 错误
Sale cashier2;    // 正确

构造函数和默认构造函数的 区别是:
当类的对象创建时, 但没有给对象的成员变量 初始化赋值时,
则编译器,会自动调用默认构造函数;

通常情况下,默认构造函数没有形参;

2.3 无形参的默认构造函数

为了创建不传递任何参数的对象,必须有一个不需要参数的构造函数,也就是默认构造函数。

如果没有这样一个默认构造函数,那么当程序尝试创建一个对象而不传递任何参数时,它将不会编译,这是因为必须有一个构造函数来创建一个对象

如果程序员没有为类编写任何构造函数,则编译器将自动为其创建一个默认构造函数。

但是,当程序员编写了一个或多个构造函数时,即使所有这些构造函数都是有参数的,编译器也不会创建一个默认构造函数,所以程序员有责任这样做。

那么,在设计一个具有构造函数的类时,应该包括一个默认构造函数,这在任何时候都会被认为是一个很好的编程实践。

类可能有许多构造函数,但只能有一个默认构造函数。
这是因为:如果多个函数具有相同的名称,则在任何给定时刻,编译器都必须能够从其形参列表中确定正在调用哪个函数。
它使用传递给函数的实参的数量和类型来确定要调用的重载函数

因为一个类名称只能有一个函数能够接受无参数,所以只能有一个默认构造函数。

2.3 有形参的默认构造函数

一般情况下,就像在 Sale 类中那样,默认构造函数没有形参。

Sale 类需要一个默认构造函数来处理免税销售的情况,但是其他类可能并不需要这样一个构造函数。

例如,如果通过类创建的对象总是希望将实参传递给构造函数。

所以有另一种的默认构造函数,其所有形参都具有默认值,所以,它也可以无实参调用。

如果创建了一个接受无实参的构造函数,同时又创建了另外一个有参数但允许所有参数均为默认值的构造函数,那么这将是一个错误,因为这实际上是创建了两个“默认”构造函数。以下语句就进行了这种非法的声明:

class Sale //非法声明,  不应该存在两个 默认构造函数;
{
    private:
        double taxRate;
    public:
        Sale()    //无实参的默认构造函数
        {
            taxRate = 0.05;
        }
        Sale (double r = 0.05)    //有默认实参的默认构造函数
        {
            taxRate = r;
        }
        double calcSaleTotal(double cost)
        {
            double total = cost + cost * taxRate;
            return total;
        };
};

可以看到,第一个构造函数没有形参,第二个构造函数有一个形参,但它有一个默认实参。如果一个对象被定义为没有参数列表,那么编译器将无法判断要执行哪个构造函数。

2.4 各自构造函数的写法

2.4.1 有形参的普通构造函数

具有形参的普通构造函数,可以有两种写法:

  1. 初始化列表的方式:

构造函数名称( 初始化值1, 初始化值2 ):成员变量1(初始化值1),… 成员变量n(初始化值n){}

class Student{
public:
int m_age;
int m_score;

// 列表方式,定义构造函数;
Student(int age, int score): m_age(age), m_socre(score){}

};

  1. 内部赋值方式:
    正常函数的赋值
Student(int age, int score){
   m_age = age;
   m_score = score;
}

C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。也就是说采用初始化列表的话,构造函数本体实际上不需要有任何操作,因此效率更高。

2.4.1 无形参的构造函数

函数内部赋值

Student(){
   m_age = 0;
   m_score = 0;
}

3. 拷贝构造函数

拷贝构造函数 为类对象本身的引用,
根据一个已经存在的对象复制出一个新的对象,一般在函数中,会将已经存在对象的数据成员的值复制一份到新创建的对象中;


Student(Student& S){
   m_age = s.m_age;
   m_score = s.m_score;
    cout << " 这是个 复制构造函数" << endl;
   
}

注意:若没有显示定义复制构造函数,则系统会默认创建一个复制构造函数,当类中有指针成员时,由系统默认创建的复制构造函数会存在“浅拷贝”的风险,因此必须显示定义复制构造函数。

  • 浅拷贝指的是在对对象复制时,只对对象中的数据成员进行简单的赋值,若存在动态成员,就是增加一个指针,指向原来已经存在的内存。这样就造成两个指针指向了堆里的同一个空间。当这两个对象生命周期结束时,析构函数会被调用两次,同一个空间被两次free,造成野指针。

  • 深拷贝就是对于对象中的动态成员,不是简单的赋值,而是重新分配空间。

4. 析构函数

析构函数是具有与类相同名称的公共成员函数,前面带有波浪符号(〜)。例如,Rectangle 类的析构函数将被命名为 〜Rectangle。

当对象被销毁时,会自动调用析构函数。在创建对象时,构造函数使用某种方式来进行设置,那么当对象停止存在时,析构函数也会使用同样的方式来执行关闭过程。

4.1 析构函数的特点

除了需要知道在对象被销毁时会自动调用析构函数外,还应注意以下事项:

  • 像构造函数一样,析构函数没有返回类型。
  • 析构函数不能接收实参,因此它们从不具有形参列表。
  • 由于析构函数不能接收实参,因此只能有一个析构函数。

例如,当具有对象的程序停止执行或从创建对象的函数返回时,就会发生这种情况。

下面的程序显示了一个具有构造函数和析构函数的简单类。它说明了在程序执行过程中这两个函数各自被调用的时间:

//This program demonstrates a destructor.
#include 
using namespace std;
class Demo
{
    public:
        Demo(); // Constructor prototypeDemo(); // Destructor prototype
};
Demo::Demo()    // Constructor function definition
{
    cout << "An object has just been defined,so the constructor" << " is running.\n";
}
Demo::Demo() // Destructor function definition
{
    cout << "Now the destructor is running.\n";
}
int main()
{
    Demo demoObj;    // Declare a Demo object;
    cout << "The object now exists, but is about to be destroyed.\n";
    return 0;
}
程序输出结果:
An object has just been defined, so the constructor is running.
The object now exists, but is about to be destroyed.
Now the destructor is running.

你可能感兴趣的:(#,C++,构造函数)