C++(第三天----类的内存空间,构造,初始化列表,析构,this指针,指针的动态内存)

一、类的内存空间

1、类本身是一种数据类型,在没有定义对象前是不占用内存空间的,定义对象的时候才会分配 空间

2、计算一个类的对象占用多少空间用sizeof(类名或对象)
1)类的内存空间大小是其数据成员(非静态-数据段)和虚表大小有关,跟函数成员无关
2)如果一个类中没有数据成员(空类),也没有虚表那么这个类的大小规定为1个字节

//类的实例化,也就是创建一个类的对象
GirlFriend mygirl1;
cout<<"size:"<<sizeof(GirlFriend)<<endl;

3、为什么空类的大小为1个字节
实际上,这是类结构体实例化的原因,空的类或结构体同样可以被实例化。如果定义对空的类或者结构体取sizeof()的值为0,那么该空的类或结构体实例化出很多实例时,在内存地址上就不能区分该类实例化出的实例。所以,为了实现每个实例在内存中都有一个独一无二的地址,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,所以空类所占的内存大小是1个字节。通俗一点讲 就是 用空类实例化一个对象,这个对象的实体要存在内存中,既然要存在内存中那么就需要有个地址能访问他,为了避免多个实例对象的地址 问题,所以才有了者一个字节的空间。

二、构造函数

1、概念
函数名与类名相同,函数没有返回值, 函数不需要用户调用,在创建对象的时候自动调用。构造函数专注于在对象创建的初期的构造工作,具体来讲就是对对象各个成员的初始化。
2、格式

class 类名{
  public:
      类名(形参) //构造函数,一般放在public权限下
      {                
      }
};

3、特点

  • 构造函数的函数名和类型一致,并且没有返回值,而且定义的构造函数一般是放在public权限下
class GirlFriend{
public:
    //类的构造函数,一般是放在public权限下
    GirlFriend()
    {
        cout<<"GirlFriend()"<<endl;
    }
}
  • 定义对象的时候,编译器会自动调用对应的构造函数
GirlFriend  mygirl;//自动调用了对应的构造函数  ---》 mygirl.GirlFriend();
GirlFriend  mygirl();//自动调用了对应的构造函数  ---》 mygirl.GirlFriend(); ---这里有问题   编译可能会报错  ()要么不写,要么写参数进去
  • 如果没有自定义构造函数,编译器就会自动生成默认(无参数、而且函数体是空的)的构造函数.
  • 如果我们自定义了构造函数,那么编译器就不会帮助我们生成构造函数
  • 自定义构造函数的函数体一般存放初始化信息
class GirlFriend{
public:
    //类的构造函数,一般是放在public权限下
    GirlFriend(int a)
    {
        cout<<"GirlFriend(int a)"<<endl;
    }
}
int main()
{
    //编译报错,因为我们自己定义了一个构造函数GirlFriend(int a),但是mygirl对象会调用没有参数的构造函数
    //此时类中没有这种构造函数,所以会报错
    //GirlFriend mygirl; 
    GirlFriend mygirl(10);//正确, 会自动调用 构造函数GirlFriend(int a)
}

三、构造函数的重载和默认参数
1、构造函数支持函数重载

class GirlFriend{
public:
    //类的构造函数,必须放在public权限下
    GirlFriend()
    {
        cout<<"GirlFriend()"<<endl;
    }
    GirlFriend(int a)
    {
        cout<<"GirlFriend(int a)"<<endl;
    }
    GirlFriend(int a,int b )
    {
        cout<<"GirlFriend(int a,int b )"<<endl;
    }
}

创建对象的时候根据不同的参数调用不同的构造函数

GirlFriend  mya;//调用构造函数  GirlFriend()   ----不要写成GirlFriend  mya(); 
GirlFriend  myb(10);//调用构造函数  GirlFriend(int a)
GirlFriend  myc(10,20);//调用构造函数  GirlFriend(int a,int b )

2、构造函数支持带默认参数

class GirlFriend{
public:
    //类的构造函数
    GirlFriend(int a=10,int b=20)
    {
        cout<<"GirlFriend(int a,int b )"<<endl;
    }
}

如果在类外实现:

class GirlFriend{
public:
    //类的构造函数,必须放在public权限下
    //默认参数在声明和定义的时候,其中一个写上就可以了
    GirlFriend(int a,int b); 
}
GirlFriend::GirlFriend(int a=10,int b=20)
{
    cout<<"GirlFriend(int a,int b )"<<endl;
}

创建对象的时候可以有以下几种方式:

GirlFriend  mya;
GirlFriend  myb(100);
GirlFriend  myc(100,200);

在创建对象的时候可以给定参数也可以不给定参数,如果不给定参数就用默认参数

#include<iostream>

using namespace std;

class Data{
    public:
        Data(){
            cout<<"Data()"<<endl;
        }
        Data(int a){
            valA = a;
            cout<<"Data(int a)"<<endl;
        }
        Data(int a,int b){
            valA = a;
            valB = b;
            cout<<"Data(int a,int b)"<<endl;
        }
        void show(){
            cout<<"valA:"<<valA<<endl;
        }
    private:
        int valA;
        int valB;
};


int main()
{
    //括号写法
    //1、该类的实例化对象 在创建对象的时候自动调用构造函数
    Data mya;
    //2、有些编译器不支持这种写法 会直接报错,因为把这一条语句当成 函数的声明 Data func1();
    //Data myb();
    Data myb(10);
    
    //3、new
    Data *p1 = new Data(100);

    //4、匿名对象
    Data myc = Data(1000);

    //显示写法
    Data myd = 20;
    myd.show();

    Data mye = Data(20,30);

    Data(200,300).show();

    return 0;
}

三、初始化列表

1)问题的引入

class Point{
public:
        //构造函数
        Point(int x,int y)
         {
               point_x = x;
                 point_y = y;
          }         
private:
        int point_x;
         int point_y;
};

在创建对象的时候,Point mya(0,0); 一般在构造函数里面初始化成员变量。除了可以使用这种方法之外,还可以通过参数列表的方式初始化。

2)概念
初始化列表主要是初始化成员变量,或者是调用父类的构造方法(后期继承的时候使用)
3)格式

class 类名{
public:
        //构造函数
        类名(数据类型 参数1,数据类型 参数2):成员变量1(初始化值),成员变量2(初始化值)
         {
          }         
private:
        数据类型 成员变量1;
         数据类型 成员变量2;
};

如果构造函数的定义是在类的外面定义,那么参数列表初始化是写在函数定义,声明不需要写。

class 类名{
public:
        //构造函数-只负责声明,定义在外面
        类名(数据类型 参数1,数据类型 参数2);
     
private:
        数据类型 成员变量1;
         数据类型 成员变量2;
};
//在类的外部进行定义构造函数
类名::类名(数据类型 参数1,数据类型 参数2):成员变量1(初始化值),成员变量2(初始化值)
{
}

4)例子

class Point{
public:
        //构造函数
        Point(int x,int y):point_x(x),point_y(y)
         {
          }         
private:
        int point_x;
         int point_y;
};

5)构造函数内部初始化与初始化列表初始化区别
构造函数内部初始化 必须要等对象创建完后才能调用,而参数列表初始化是在创建对象的时候就执行了(也就是定义空间的时候立即初始化 了),在后期继承的时候父类构造方法,以及const修饰的常量多要用参数列表初始化

6)const修饰类的数据成员必须在构造函数的初始化列表中初始化

const修饰的成员变量相当于该变量是一个常量,所以只能在初始化列表上初始化。

#include<iostream>

using namespace std;

class Date{
public:
        //const修饰类的数据成员必须在构造函数的初始化列表中初始化
        Date(int year,int month):_year(year),_month(month),_day(1)
        {
            //_day = 1;//const 修饰的数据成员为只读变量,不可以在构造函数内部初始化
        }
private:
    int _year;
    int _month;
    const int _day;
};

int main()
{
    Date mya();
    
    return 0;
}

四、析构函数

1、概念
函数名与类名相同在函数名前面添加~, 函数没有返回值,没有参数,当对象销毁的时候系统自动调用(可以在析构函数中释放成员空间)。析构函数专注于对象销毁期间的解构工作,具体来讲就是对对象所占据的各种资源的善后处理。
2、格式

class 类名{
public:
        //构造函数
        类名(数据类型 参数1,数据类型 参数2)
         {
            //存放一些初始化的语句
          }
         //析构函数
        ~类名()
         {
            //存放资源释放的语句
          }                   
private:
        数据类型 成员变量1;
         数据类型 成员变量2;
};

3、例子

#include <iostream>
using namespace std;//引进命名空间

class A
{
public:
    //自定义析构函数
    ~A()
    {
        cout<<"~A()"<<endl;        
    }
protected:

private:
    
};
//入口函数
int main(int argc,char * argv[])
{ 
    A mya;
    return 0;
}

五、this指针

以上述 BMP 类为例,假如在其构造函数中,保存其文件名:

class BMP
{
    string fileName;
    int width;
    int height;

    char *RGB;

public:
    // 构造函数:
    //底层会自动转换为BMP(BMP*this,string fileName)
    BMP(string fileName)
    {
        fileName = fileName; // 奇怪的赋值
    }
};

上述代码显然有误,由于类成员 fileName 恰好与构造函数参数同名,因此其赋值语句发生了名字冲突,无法正常赋值,此时除了修改它们的名字外,通常也可以用 this 指针来指明变量的所属,比如:

// 构造函数
BMP(string fileName)
{
    this->fileName = fileName; 
}

这样,左侧的 fileName 跟右侧的 fileName 就不会混淆了。
重点:

  • this 指针是所有类方法(包括构造函数和析构函数)的隐藏参数
  • this 指向调用了该类方法的类对象

六、指针和动态内存

1、指针变量
用来存储地址的变量,在C语言中,如果地址类型不一致,只会报警告,而在C++中会更为严格会直接报错,所以在C++中类型必须要一致。
比如:

在C语言中
int *p = 0x12345678; //左边是int*类型,右边是int类型,类型不一致会警告
在C++int *p = 0x12345678;会直接报错
必须强转为相同类型
int *p = (int*)0x12345678;
#include<stdio.h>
int main()
{
    //int *p = 0x12345678;//直接报错
    int *p = (int *)0x12345678;//必须将右边强转为相同的数据类型
    
    return 0;
}

2、动态内存的申请和释放

1)C/C++申请的方式
c语言方式 --函数
申请堆空间 malloc, calloc
释放堆空间 free
c++语言方式 —运算符(或者也可以说是关键字)
申请堆空间 new =====》 malloc+构造函数
释放堆空间 delete ===》 free+析构函数
2)申请格式

数据类型  *变量名 = new  数据类型;
数据类型  *变量名 = new  数据类型(初始值); //申请内存空间的时候直接初始化
数据类型  *变量名 = new  数据类型[数据元素个数];

比如:

char  *p  = new  char;     申请一个char对象空间
char  *p  = new  char('a');   申请一个char对象并且初始化为a
char  *p  = new  char[100] ;  申请100char对象(空间是连续) ---数组 
int (*p)[5] = new int[3][5];  二维数组

3)释放格式

delete  指针变量;  释放单个对象
delete  []指针变量;  释放连续的多个对象

总结:

  • C语言中的malloc free 是函数,C++语言中的new delete是运算符
  • new 在申请内存的同时,还会调用对象的构造函数,而 malloc 只会申请内存;同样,delete 在释放内存之前,会调用对象的析构函数,而 free 只会释放内存。

练习4:

  1. 说出下面定义的四个对象之间的区别
    1 (a) int ival = 1024; © int *pi2 = new int( 1024 );
    2 (b) int *pi = &ival; (d) int *pi3 = new int[ 1024 ]

练习5:通过动态分配内存分配一个int数组,每个成员的值和他的下标一致,遍历数组并打印每个成员 的值
练习6:通过动态分配内存的方式存储从键盘上获取同学的信息,并且可以显示出来
[1]新增 [2]显示

#include<iostream>

using namespace std;

int main()
{
    //申请一片连续的内存空间,一共有5个元素,每个元素都是int类型
    int *array = new int[5];
    int i;
    
    for(i=0; i<5; i++)
    {
        array[i] = i;
    }
    for(i=0; i<5; i++)
    {
        cout<<"\t"<<array[i];    
    }
    cout<<endl;
    
    //释放连续的内存空间
    delete []array;
    
    return 0;
}

扩展:

//设置左对齐 设置小数点后精度为8位 设置精度固定
cout<<setiosflags(ios::left)<<setprecision(8)<<setiosflags(ios::fixed);  

void print()
{
    cout<<"------------------------------"<<endl;
    cout<<setw(15)<<"name";
    cout<<setw(15)<<"age";
    cout<<setw(15)<<"score"<<endl;
    for(int i=0; i<count; i++)
    {
        cout<<setw(15)<<ptr[i].name;
        cout<<setw(15)<<ptr[i].age;
        cout<<setw(15)<<ptr[i].score<<endl;
    }
    cout<<"------------------------------"<<endl;
}

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 02:36:08       50 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 02:36:08       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 02:36:08       43 阅读
  4. Python语言-面向对象

    2024-07-10 02:36:08       54 阅读

热门阅读

  1. 基于 LSN 的 PostgreSQL 数据管理与恢复

    2024-07-10 02:36:08       22 阅读
  2. 加密货币安全升级:USDT地址监控机器人

    2024-07-10 02:36:08       20 阅读
  3. bind方法的使用

    2024-07-10 02:36:08       15 阅读
  4. 128陷阱详解

    2024-07-10 02:36:08       15 阅读
  5. 前端如何控制并发请求

    2024-07-10 02:36:08       15 阅读
  6. ubuntu虚拟机安装

    2024-07-10 02:36:08       20 阅读
  7. RabbitMQ安装使用遇到的问题

    2024-07-10 02:36:08       18 阅读
  8. ShardingSphere

    2024-07-10 02:36:08       20 阅读
  9. Docker启动安装nacos

    2024-07-10 02:36:08       20 阅读
  10. 设置Nginx响应超时配置

    2024-07-10 02:36:08       25 阅读
  11. 每周算法(week2)【leetcode11~30】

    2024-07-10 02:36:08       21 阅读