有对象一定要有空间,有空间不一定有对象。
class Empty
{
};
int main()
{
Empty e;
cout << sizeof(e) << endl;//1字节
return 0;
}
虽然此对象没任何属性和方法,但是要创建一个对象,就必须在地址空间标识此对象,就必须占有一个字节。
类的数据成员不能在类定义时初始化。
class student
{
int num = 20060102;
char name[15] = "张三";
float score = 85;
};//错误
如果一个类中所有的数据成员都是公用的public,则可以在定义对象时对数据成员进行初始化。
class studet
{
public:
int num;
char name[15];
float score;
}stu1 = { 20010130101,"张三",85};
一、构造函数
数据成员多为私有的,要对它们进行初始化,必须用一个公有函数来进行。C++提供了构造函数来处理类对象的初始化问题。同时这个函数应该在且仅在定义对象时自动执行一次。称为构造函数(constructor) 。
构造函数就是用来在创建对象时初始化对象,为对象数据成员赋初始值。
构造函数用途:1)创建对象,2)初始化对象中的属性,3)类型转换。
构造函数是类的一种特殊的成员函数(在特殊用途中构造函数的访问限定可以定义成私有或保护)
,不需要人为调用;而是在建立对象时自动被执行。
特征:
1.C++规定构造函数的名字必须与类名相同。
2.构造函数无函数返回类型说明。注意是没有而不是void,即什么也不写,也不可写void。实际上构造函数有返回值,返回的就是构造函数所创建的对象。
3.在程序运行时,当新的对象被建立,该对象所属的类构造函数自动被调用,在该对象生存期中也只调用这一次(由系统调用)。
4.构造函数可以重载。严格地讲,类中可以定义多个构造函数,它们由不同的参数表区分,系统在自动调用时按一般函数重载的规则选一个执行。
5.构造函数可以在类中定义,也可以在类中声明,在类外定义。
6.如果类说明中没有给出构造函数,则C++编译器自动给出一个缺省的构造函数.
类名(void){ }
只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。只要构造函数是无参或只要各参数均有缺省值的,C++编译器都认为是缺省的构造函数,并且缺省的构造函数只能有一个。默认构造函数——除了this指针以外没有参数的构造函数,函数体是空的,只能为对象开辟数据成员存储空间,而不能给对象中的数据成员赋初值。
构造函数定义形式:
类名(形式参数列表)
{ 函数体 }
1.1不带参数的构造函数
构造函数可以没有形参。
1>在类内定义构造函数:
#include<iostream>
using namespace std;
class student
{
public:
student()
{
num = 20060102;
strcpy(name,"张三");
score = 85;
}
void display()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "score:" << score << endl;
}
private:
int num;
char name[15];
float score;
};
2>在类外定义构造函数:
#include<iostream>
using namespace std;
class student
{
public:
student();//类外定义构造函数
void display()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "score:" << score << endl;
}
private:
int num;
char name[15];
float score;
};
student::student()
{
num = 20060102;
strcpy_s(name, "张三");
score = 85;
}
只要创建类的新对象,都要执行构造函数。定义对象时自动调用构造函数
构造函数的主要用途就是:初始化类的数据成员。
用构造函数对对象进行初始化形式:
无参数的构造函数,定义对象的形式:
类名 对象名1,对象名2,...... ;
有参数的构造函数,定义对象的形式:
类名 对象名1(实参列表),对象名2(实参列表)...... ;
构造函数是一种成员函数,它具有一般成员函数的特点。
构造函数的名称与其类名相同。
一个类中可定义一个或多个构造函数。
构造函数说明:
1) 构造函数是在创建对象时自动执行的,而且只执行一次,并先于其他成员函数执行。构造函数不需要人为调用,也不能被人为调用。
2) 构造函数一般声明为公有的(public),因为创建对象通常是在类的外部进行的。
3) 在构造函数的函数体中不仅可以对数据成员初始化,而且可以包含其他任意功能的语句,但是一般不提倡在构造函数中加入与初始化无关的内容。
用不带参数的构造函数对数据成员初始化,每一个对象的数据成员都得到同一组初值。
1.2带参数的构造函数
用带参数的构造函数对不同对象初始化
1>在类内定义构造函数
class student
{ public:
student(int n, string m, float s)
{ num = n;
name = m;
score = s;
}
void display( )
{ cout<<”num: ”<< num<<endl;
cout<<”name: ”<< name<<endl;
cout<<”score: ”<< score <<endl;
}
private:
int num;
string name; // 或char *name;
float score;
} ;
2>在类外定义构造函数
class student
{ public:
student(int n, string m, float s);
void display( )
{ cout<<”num: ”<< num<<endl;
cout<<”name: ”<< name<<endl;
cout<<”score: ”<< score <<endl;
}
private:
int num;
string name; // 或 char *name;
float score;
} ;
student::student(int n, string m, float s )
{ num = n;
name = m;
score = s;
}
1.3在构造函数中用参数初始化表对数据成员初始化
构造函数可以使用参数初始化表对数据成员进行初始化,不在函数体内,而在函数首部实现。
一般形式为:
类名(形式参数列表):构造函数初始化
{ 函数体
}
与其他的成员函数一样,构造函数可以定义在类的内部或外部,但带初始化表的构造函数只在类体中定义中。
class student
{ public:
student(int n, string m, float s):num(n),name(m),score(s){ }
void display( )
{ cout<<”num: ”<< num<<endl;
cout<<”name: ”<< name<<endl;
cout<<”score: ”<< score <<endl;
}
private:
int num;
string name; // 或char *name;
float score;
} ;
注意:如果数据成员是数组,就不能在参数初始化表对其进行初始化,应在函数体中用语句对其赋值。
初始化是在构建对象的时候赋值,赋值是在构建完成后才会赋值。
class student
{ public:
student(int n, float s, char m[ ] ):num(n), score(s)//此处是初始化
{ strcpy(name, m);//函数体中的叫赋值 }
void display( )
{ cout<<”num: ”<< num<<endl;
cout<<”name: ”<< name<<endl;
cout<<”score: ”<< score <<endl;
}
private:
int num;
char name[15];
float score;
} ;
初始化列表的顺序,并不是对象中成员初始化表的顺序,是按照类中属性声明的顺序构建
class Complex
{
private:
int Real;
int Image;
public:
Complex(int x) :Real{Image},Image{x}{}
void Print()
{
cout << Real << " " << Image << endl;
}
};
int main()
{
Complex c1(10);
c1.Print();
return 0;
}
1.4构造函数重载
与一般的成员函数一样,在一个类中也可以定义多个构造函数,即构造函数重载,只要每个构造函数的形参列表是唯一的。一个类的构造函数数量是没有限制的。
#include <iostream>
using namespace std;
class student
{ public:
student( );
student(int n, string m, float s):num(n),name(m),score(s){ }
void display( ) ;
private:
int num;
string name;
float score;
} ;
student::student( )
{ num = 20060102;
name = ”张三”;
score = 85;
}
void student::display( )
{ cout<<”num: ”<< num<<endl;
cout<<”name: ”<< name<<endl;
cout<<”score: ”<< score <<endl;
}
int main( )
{ student stu1;
stu1.display( );
student stu2( 20060103,“李四”, 88);
stu2.display( );
return 0;
}
1)在建立对象时不必给出实参的构造函数,称为默认构造函数。无参构造函数属于默认构造函数。一个类只能有一个默认构造函数。
2)如果未定义构造函数,系统会自动提供一个默认构造函数,但它的函数体是空的,不起初始化作用。
3)尽管在一个类中可以包含多个构造函数,但是对于一个对象来说, 建立对象时只执行其中一个,并非每个构造函数都被执行。
1.5带默认参数的构造函数
在实际应用中,有些构造函数的参数值通常是不变的,只是在一些特殊情况下才需改变其值——可定义带默认参数的构造函数。
#include<iostream>
#include<string>
using namespace std;
class student
{
public:
student(int n = 20060101, string m = "张三", float s = 85);
void display();
private:
int num;
string name;
float score;
};
student::student(int n, string m, float s)
{
num = n;
name = m;
score = s;
}
void student::display()
{
cout << "num: " << num << endl;
cout << "name:" << name << endl;
cout << "score:" << score << endl;
}
int main()
{
student stu1;
stu1.display();
student stu2(20060103,"李四");
stu2.display();
return 0;
}
1)必须在类的内部指定构造函数的默认参数,不能在类外部指定默认参数。
class student
{ public:
student( int n , string m , float s );
void display( ) ;
private:
int num;
string name;
float score;
} ;
student::student( int n=20060101, string m=”张三”, float s=85 )
{ num = n;name = m; score = s; } //错误
2)如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。这时,就与无参数的构造函数有歧义了。
3)在一个类中定义了带默认参数的构造函数后,不能再定义与之有冲突的重载构造函数。
一般地,不应同时使用构造函数的重载和带默认参数的构造函数。
1.6用构造函数实现初始化方法总结
1)在类中定义的构造函数的函数体中对数据进行赋初值。
2)用带参数的构造函数,可以使同类的不同对象中的数据具有不同的初值。
3)在构造函数中用参数初始化表对数据赋初值。
4)在定义构造函数时可以使用默认参数。
5)构造函数可以重载,即在一个类中定义多个同名的构造函数。
一般不应同时使用有默认参数的构造函数和构造函数的重载。
#include<iostream>
using namespace std;
class CGoods
{
private:
enum{LEN=20};//枚举类型大小不计入结构体大小中
private:
char Name[LEN];//直接将LEN替换为20
int Amount;
float Price;
float Total;
public:
CGoods(const char*name,int amount=0,float price=0.0):Amount(amount),Price(price),Total(amount*price)
{
strcpy_s(Name, name);
}
CGoods(){}
void GoodsInfo()const
{
cout << " Name " << Name << " Amount: " << Amount << " Price " << Price << " Total_value: " << Total << endl;
}
};
int main()
{
CGoods ca;
CGoods car("bya", 10, 12.00);
CGoods book("c++", 10, 128.00);
car.GoodsInfo();
book.GoodsInfo();
return 0;
}
1.7 对象的定义
下列定义的对象哪一个是错误的:
class Complex
{
public:
Complex(){}
Complex(int r,int i):Real(r),Image(i){}
void Print()
{
cout << Real << " " << Image << endl;
}
private:
int Real;
int Image;
};
int main()
{
Complex c1;
Complex c2();
Complex c3(12, 23);
Complex c4 = Complex(1, 2);//直接将创建的对象给c4,没有借助中间对象,相当于Complex c4(1,2);
c4 = Complex(3,4);//此种情况是创建一个无名对象,将无名对象的值给c4,因为c4此时存在,如果直接将构建的值给c4,那么c4就被构建了两次,这是不允许的
Complex c5{};
Complex c6{ 1,2 };
c1.Print();
return 0;
}
定义的c2不是对象
c2系统认为是函数的声明,系统将 类型 名称 ();认为是函数的声明
如何想用()初始化对象,要么就要带默认值(eg:c3),不带默认值就不要写括号(c1)
1.8构造函数的用途——类型转换
class Int
{
private:
int value;
public:
Int(int x=0):value(x){}
void Print()const { cout << value << endl; }
};
int main()
{
Int a(10);//自己设计的类型
a.Print();//10
int x = 100;//系统内置类型
a = x;//将int类型的x赋值给Int类型的对象a
//隐式转换,用x的值构建一个临时对象,将这个临时对象赋值给a,赋值完成后,临时对象销毁,此临时对象称为将亡值
a = (Int)x;//显式转换
a.Print();//100
return 0;
}
把一个变量给对象时,看起来像是把变量给对象赋值,实则不然。
把一个变量给对象,先调动对象的构造函数,构建一个临时量,再把这个临时量赋值给对象。赋值完成后,临时量就会销毁,此临时量也称作将亡值。
关键字explicit
不允许隐式转化
构造函数要想可以类型转换,必须只有一个参数或者参数有默认值。
class Int
{
private:
int value;
public:
Int(int x,int y):value(x+y){}
void Print()const { cout << value << endl; }
};
int main()
{
Int a(10,20);
a.Print();
int x = 1, y = 2;
a = x, y;//error
a = (Int)(x, y);//强转也不行
return 0;
}
构造函数有了默认值就可以了,就剩一个没默认值的参数
class Int
{
private:
int value;
public:
Int(int x,int y=0):value(x+y){}
void Print()const { cout << value << endl; }
};
int main()
{
Int a(10,20);
a.Print();//30
int x = 1, y = 2;
a = x, y;
//强转,只能是单参
a = (Int)(x, y);//构造函数此时单参,只需要传一个参数,逗号表达式的值为最右边的值
a.Print();//2
//构建一个对象给a
a = Int(x, y);
a.Print();//3
return 0;
}
二、析构函数
当对象脱离其作用域时(如对象所在的函数已调用完毕),系统会自动执行析构函数
析构函数也是一种特殊的成员函数,执行的是与构造函数相反的操作——通常用作执行一些清理任务(如释放对象所占用得内存等)。
析构函数定义形式:
~类名( )
{
函数体
}
2.1析构函数的特点:
1.析构函数与构造函数的名字相同,但在其前面加上“~”。
2.析构函数没有任何参数,没有返回值,也没有返回类型不能重载
3.一个类中只能有一个析构函数。
4.对象销毁时,系统自动调用析构函数。
5.如果类说明中没有给出析构函数,则C++编译器自动给出一个缺省的析构函数。
eg: ~类型名称(){}
class student
{
public:
student(int n, string m, float s)
{
num = n;
name = m;
score = s;
}
~student()
{}
void display()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "score:" << score << endl;
}
private:
int num;
string name;
float score;
};
int main()
{
student stu1(2020, "zhangsan", 80);
stu1.display();
student stu2(2021, "lisi", 90);
stu2.display();
return 0;
}
2.2析构函数说明:
1.每个类必须有一个析构函数,如果未定义析构函数,则系统自定义一个析构函数;
2.对于大多数类而言,不需要显式地编写析构函数,尤其是具有构造函数的类不一定需要定义自己的析构函数。析构函数通常用于释放在构造函数或在对象生命期内获取的资源(如动态分配的内存)。
2.3何时调用析构函数:
1)对象在程序运行超出其作用域时自动撤销,撤销时自动调用该对象的析构函数。如函数中的非静态局部对象。
2)如果用new运算动态地建立了一个对象,当用delete运算释放该对象时,调用该对象的析构函数。
2.4调用构造函数和析构函数的顺序
在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用次序。
构造函数和析构函数的调用很像一个栈的先进后出,调用析构函数的次序正好与调用构造函数的次序相反。最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。
先构造的后析构,后构造的先析构。
三、对象的生存期
3.1 局部对象
1>对于局部定义的对象,每当程序控制流到达该对象定义处时,调用构造函数。当程序控制走出该局部域时,则调用析构函数。
class Complex
{
private:
int Real;
int Image;
public:
Complex(int r=0,int i=0) :Real(r), Image(i)
{
cout << "Create Complex " << endl;
}
~Complex() {
cout << "Destory Complex" << endl;
}
void Ptint()const
{
cout << "Real: " << Real << "Image: " << Image << endl;
}
void Print()
{
cout << Real << " " << Image << endl;
}
};
void fun()
{
Complex c1(1, 2);
cout << "input block" << endl;
{
Complex c2(4, 5);
}
cout << "out block" << endl;
return;
}
2>对于静态局部定义的对象,在程序控制首次到达该对象定义处时,调用构造函数。当整个程序结束时调用析构函数。
单纯的局部对象,tmp对象一直在构建析构构建析构,总共10次
class Complex
{
private:
int Real;
int Image;
public:
Complex(int r=0,int i=0) :Real(r), Image(i)
{
cout << "Create Complex " << endl;
}
~Complex() {
cout << "Destory Complex" << endl;
}
void Ptint()const
{
cout << "Real: " << Real << "Image: " << Image << endl;
}
void Print()
{
cout << Real << " " << Image << endl;
}
};
void fun()
{
Complex tmp(1, 2);
}
int main()
{
for (int i = 0; i < 10; ++i)
{
fun();
}
return 0;
}
当fun中的对象为静态局部对象时
void fun()
{
static Complex tmp(1, 2);//静态对象,在数据区,有一个标记,运行时下次再遇到查看标记发小存在就不再构建
}
int main()
{
for (int i = 0; i < 10; ++i)
{
fun();
}
return 0;
}
在函数第一次调用时只构建一次,也析构一次。
3.2 全局对象
对全局定义的对象,当程序进入入口函数main之前对象就已经定义,这时要调用构造函数。整个程序结束时调用析构函数。
例:下列对象a,b,c,哪一个先构建
class Int
{
private:
int value;
public:
Int(int x = 0) :value(x)
{
cout << "Create Int: " << value<<endl;
}
~Int()
{
cout << "Destory Int: " << value << endl;
}
};
Int a(1);
int main()
{
Int b(2);
return 0;
}
Int c(3);
我们可以看到,a,c,b按照此顺序构建
a和c是全局对象,b是主函数中的对象,在进入主函数之前,全局变量和全局对象就要构建出来,进入主函数之后,主函数中的对象才会被构建
3.3 动态创建的对象
动态创建的对象,使用new创建对象,delete释放对象.
malloc申请空间
1>下列程序可以运行成功吗?show可以被cp指针调用吗
class Complex
{
private:
int Real;
int Image;
public:
Complex(int r = 0, int i = 0) :Real(r), Image(i)
{
cout << "Create Complex " << endl;
}
~Complex() {
cout << "Destory Complex" << endl;
}
void Print()const
{
cout << "Real: " << Real << "Image: " << Image << endl;
}
void show()
{
cout << "Complex::show " << endl;
}
};
int main()
{
Complex* cp = nullptr;
cp->show();
return 0;
}
//show(cp),传的是个空指针,this指针为nullptr,但是show方法中没有用到this指针,所以可以调用show
class Complex
{
private:
int Real;
int Image;
public:
Complex(int r = 0, int i = 0) :Real(r), Image(i)
{
cout << "Create Complex " << endl;
}
~Complex() {
cout << "Destory Complex" << endl;
}
//void Print( Complex *const this)const
void Print()const
{
//cout << "Real: " << this->Real << "Image: " << this->Image << endl;
cout << "Real: " << Real << "Image: " << Image << endl;
}
void show()
{
cout << "Complex::show " << endl;
}
};
int main()
{
Complex* cp = nullptr;
cp->Print();
//Print(cp),this指针为nullptr
return 0;
}
2>那么cp可以调用print方法吗?
cp->print();
我们可以看到并不能调用成功。
Print方法中含有this指针,此时cp为nullptr,this指针为空,0地址不允许操作,
3>使用malloc申请空间的对象调用print方法可以成功吗
int main()
{
Complex* cp = (Complex*)malloc(sizeof(Complex));
if (cp == nullptr)return 1;
cp->Print();
return 0;
}
我们可以看到可以调用print方法,但是值为随机值。
有空间但是没对象,
malloc给cp申请了4字节(X86)的空间,Print(cp);cp指向申请的空间,this指针也指向这个堆区空间,但是没有对象,所以值为随机值。
给cp构建一个对象:
int main()
{
Complex c1(1, 2);
Complex* cp = (Complex*)malloc(sizeof(Complex));
if (cp == nullptr)return 1;
cp->Print();
*cp = c1;
cp->Print();
return 0;
}
此时cp的值就不为随机值了
不允许拿对象给空间赋值,可以在空间中构建对象,但不允许用对象给空间赋值。
如果拿对象给空间赋值,容易出问题,eg:virtual虚函数
virtual void Print()const
{
cout << "Real: " << Real << "Image: " << Image << endl;
}
new申请空间
class Complex
{
private:
int Real;
int Image;
public:
Complex(int r = 0, int i = 0) :Real(r), Image(i)
{
cout << "Create Complex " << endl;
}
~Complex() {
cout << "Destory Complex" << endl;
}
virtual void Print()const
{
cout << "Real: " << Real << "Image: " << Image << endl;
}
void show()
{
cout << "Complex::show " << endl;
}
};
int main()
{
Complex* cp = new Complex(1, 2);//new 1.自动计算类型大小 2.调用malloc从堆区申请空间 3.调动构建函数构建对象 4.把对象地址给cp
cp->Print();
delete cp;//1.调用析构函数析构cp 2.将cp的堆空间还给系统,cp此时变成失效指针,main函数结束时,cp栈区空间才会释放
cp = nullptr;
return 0;
}
四、new三种用法:
4.1作为运算法(关键字)
p = new Pointer(10,20);//p是指针,Pointer 是类对象
new调用malloc在heap申请空间,调用Pointer类型在空间构建对象,返回。
4.2定位(构建)new
只要给定位new一个地址,就可以在此地址创建对象。
new(s) Pointer(12,23);//在s指向的空间调动构造函数,构建对象
delete p;//delete调动析构函数释放空间,再调用free销毁。
#include<iostream>
using namespace std;
class Object
{
private:
int val;
public:
Object(int x)
{
val = x;
cout << "Create:" << val << endl;
}
};
Object ObjA(1);
int main()
{
Object Objb(2);
}
Object objc(3);
进入主函数之前就要为全局对象构建,再进入主函数编译从上到下,
int main()
{
int a = 10;
int b(10);//相当于调动了一个带参数的构造函数,拿10构建b
/*伪构造函数*/
int c = int(10);//类型名+(),调动其构造函数,构建c对象
int* p = new int(20);//new申请int空间,调动int的构造函数构造整型
}
一个对象只能被构建一次,但可借助new实现二次构建,借助定位new可以对任何对象重新构建。
class Complex
{
public:
Complex(){}
Complex(int r,int i):Real(r),Image(i){}
void Print()
{
cout << Real << " " << Image << endl;
}
private:
int Real;
int Image;
};
int main()
{
Complex c1;
Complex c2(12, 23);
c1(23,34);//erro,对象只能构建一次
new(&c1)Complex(23, 34);
c1.Print();
c2.Print();
return 0;
}
4.3作为一个函数
int main()
{
/* operator new,函数方法的使用,和malloc一样,需要sizeof计算空间大小,需要对返回值强转*/
int* ip = new int(10);
int* is = (int*)::operator new(sizeof(int));
/*operator new 和 malloc 的不同*/
//malloc申请空间空间不足会返回空指针
//operator new 申请空间失败会抛出异常throw bad_alloc,所以使用new或operator new 判断是否申请成功不能用判空判断
//可以使用nothrow,让new或者operator new申请空间失败不抛出异常,返回一个空指针
int* ip = new(std::nothrow) int(10);
int* is = (int*)::operator new(sizeof(int),std::nothrow);
delete ip;//new 申请的空间用delete释放
::operator delete(is);//operator new 申请的空间需要用::operator delete释放
return 0;
}