一、类的内存空间
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] ; 申请100个char对象(空间是连续) ---数组
int (*p)[5] = new int[3][5]; 二维数组
3)释放格式
delete 指针变量; 释放单个对象
delete []指针变量; 释放连续的多个对象
总结:
- C语言中的malloc free 是函数,C++语言中的new delete是运算符
- new 在申请内存的同时,还会调用对象的构造函数,而 malloc 只会申请内存;同样,delete 在释放内存之前,会调用对象的析构函数,而 free 只会释放内存。
练习4:
- 说出下面定义的四个对象之间的区别
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;
}