例如下面的代码,对于Table类,可以通过t.Set公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,就会有点麻烦,那能否在对象创建时,就将信息设置进去?
而我们的需求是不通过对象去调用初始化对象的数据,我们希望当这个对象创建出来的时候,他就已经是具有一定的初始值的,
为解决完成对象的初始化问题,引出构造函数概念
class Table
{
public:
void Set();
void Print();
private:
int m_length;
int m_width;
int m_height;
};
void Table::Set()//设置长宽高
{
m_length = 120;
m_width = 40;
m_height = 80;
}
void Table::Print()//打印长宽高
{
cout<<m_length<<" "<<m_width<<" "<<m_height<<endl;
}
void main()
{
Table t;//此代码一运行,对象t就已经被定义,但其属性没有确定,就存在一定问题
//t.Set();
t.Print();//没有运行Set,输出结果为随机值
}
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。
特殊的成员函数,函数名和类名相同,无返回类型,可以带参数(意味着可以重载);
函数名与类名相同。
无返回值。
编译器自动调用对应的构造函数。
构造函数可以重载。
构造函数的功能是用来完成对象的初始化的,需要注意的一点是,虽然构造函数叫"构造"函数,但是构造函数并不是用来构造对象的,
在定义对象的时候,自动调用当前类的构造函数;
如果程序员没有定义构造函数,则类会提供一个默认的构造函数,给类中的数据成员分配空间(栈上的空间)。
遇到对象,要自动调用当前类的构造函数,调用构造函数的步骤:(基础,后续会扩充)
1、传参;
2、根据数据成员在类中的声明顺序开辟空间;
3、执行构造函数函数体;
class Table
{
public:
//如果程序员没有写,类会提供一个默认的构造函数,默认构造函数是无参数
Table(int l = 120,int w =40,int h= 80)
//Table(int l,int w,int h)//会报错,要不改成无参,要不加值
{
m_length = l;
m_width = w;
m_height = h;
cout<<"Table:"<<endl;
}
void Print()
{
cout<<m_length<<" "<<m_width<<" "<<m_height<<endl;
}
private:
int m_length;
int m_width;
int m_height;
};
int main()
{
Table t;//定义对象 --- 自动调用Table的构造函数
t.Print();
Table t1(1);
t1.Print();
Table t2(1,1);
t2.Print();
Table t3(1,1,1);
t3.Print();
cout<<" "<<endl;
Table tt[5];//定义了一个对象数组 tt[0] ~ tt[4],会定义5次
cout<<"main"<<endl;
cout<<" "<<endl;
Table *p = &t;//p,不是对象,只是一个指向Table类型的指针,占四字节空间
p->Print();
//int *p;类比于int
}
运行结果:
组合:强拥有,一个类的对象作为另外一个类的数据成员,整体和部分的关系, 生存周期是一样的
类比于聚合:弱拥有,
注意以下示例代码,类的声明是根据数据成员在类中的声明顺序来定的。
class CPU
{
public:
CPU()//仅仅用来表示调用声明顺序
{
cout<<"CPU"<<endl;
}
};
class Mouse
{
public:
Mouse()
{
cout<<"Mouse"<<endl;
}
};
class KeyBoard
{
public:
KeyBoard()
{
cout<<"KeyBoard"<<endl;
}
};
class Computer
{
public:
Computer()
{
cout<<"Computer"<<endl;
}
private:
CPU cpu;
Mouse ms;
KeyBoard kb;
};
void main()
{
Computer c;
}
运行结果:
一个类的对象作为另一个对象的数据成员时,例如以下代码,定义Student对象时,定义其成员函数int m_num;char m_name[20];Date birthday;时,定义birthday时,会先调用类Date,先调用其构造函数Date,所以在定义birthday时,就已经将其初始化,开辟完空间后,已有值,给了初始化的值。
在运行到birthday = d;时,其是赋值,给其赋我想要的值,而不是初始化。所以运行Date d(2001,1,1);时,不能一次赋值所需要的值,不能在开辟空间的时候赋给所需要的值。
类似于(先初始化,在赋值):
int a = 0;//初始化
a = 10;//赋值
class Date
{
public:
//Date(int y,int m,int d)
Date(int y=2000,int m=12,int d=12)//得带初始值,否则报错
{
m_y = y;
m_m = m;
m_d = d;
cout<<"Date"<<m_y<<" "<<m_m<<" "<<m_d<<endl;
}
void Show()
{
cout<<m_y<<" "<<m_m<<" "<<m_d<<endl;
}
private:
int m_y;
int m_m;
int m_d;
};
class Student
{
public:
Student(int num,char *name,Date d)
{
m_num = num;
strcpy(m_name,name);
birthday = d;
}
void Print()
{
cout<<m_num<<" "<<m_name<<" ";
//cout<
birthday.Show();
}
private:
int m_num;
char m_name[20];
Date birthday;//到这一步,没有办法传参
};
int main()
{
Date d(2001,1,1);
Student s(1001,"lisi",d);
s.Print();
}
Date(int y,int m,int d) 的报错:
类成员初始化的困惑 — 冒号语法 — 成员初始化列表
int a = 10;
int a = 0;//初始化
a = 10;//赋值
所以为避免这种情况,需要在初始化的时候,不进入到构造函数函数体内。既传参后,赋值前。()后{}前
:m_num(num) — 把num的值赋值给m_num
可以等价替换的,用()
写在:后为初始化,写在{}内为赋值
class Date
{
public:
//可以不带默认值,如果当前的类的构造函数里面的参数没有写默认值,那么必须在另外一个类的那个构造函数的冒号语法处显示的传参
Date(int y,int m,int d):m_y(y),m_m(m),m_d(d)
{
//cout<<"Date"<
}
void Show()
{
cout<<m_y<<" "<<m_m<<" "<<m_d<<endl;
}
private:
int m_y;
int m_m;
int m_d;
};
class Student
{
public:
//Student(int num,char *name,Date d):birthday(d)
//Student(int num,char *name,Date d):birthday(y,m,d)//error,m_m不能直接赋值
Student(int num,char *name,int y,int m,int d):m_num(num),birthday(y,m,d)
{
//m_num = num;
strcpy(m_name,name);
//birthday = d;
}
//Student(int num,char *name,Date d)
//{
// m_num = num;
// strcpy(m_name,name);
// birthday = d;
//}
void Print()
{
cout<<m_num<<" "<<m_name<<" ";
//cout<
birthday.Show();
}
private:
int m_num;
char m_name[20];
Date birthday;//到这一步,没有办法传参
};
int main()
{
//Date d(2001,1,1);
//Student s(1001,"lisi",d);//困惑在于不能一次性初始化Student类,需要先初始化其中的Date类
Student s(1001,"lisi",2001,5,15);
s.Print();
}
运行结果
1、传参;
2、根据数据成员在类中的声明顺序,用冒号语法后面的值继续初始化;
3、执行构造函数函数体;
//笔试题
//根据数据成员在类中的声明顺序,用冒号语法后面的值继续初始化;
//先声明m_i,再声明m_j
class A
{
public:
A(int i = 0,int j = 0):m_j(j),m_i(m_j)//先声明m_i,在声明m_j
//即先运行m_i(m_j),在运行m_j(j)
{
}
void print()
{
cout<<m_i<<" "<<m_j<<endl;
}
private:
int m_i;
int m_j;
};
void main()
{
A a(4,8);
a.print();
}
如果没有冒号语法,那么数据成员不能有常量和引用
//冒号语法,解决了类成员是常量和引用时,不能更改的情况
class A
{
public:
A(int i,int j):m_i(i),m_j(j)
{
//error,不能赋值
/*m_i = i;
m_j = j;*/
}
private:
//const int m_i = 7;//error,不能在这初始化
const int m_i;//常量
int &m_j;//引用
};
void main()
{
//error,常量必须在声明的时候初始化
//const int a;
//a = 7;
//error,引用必须在声明的时候初始化
/*int a = 10;
int &b;
b=a*/;
}
class A
{
public:
A(){cout<<"A"<<endl;}
};
A a;
void main()
{
cout<<"main"<<endl;
}
静态变量:
静态变量的开辟空间,是由程序员决定,只有程序员使用时才在堆里开辟空间。在第一次遇到当前对象的时候开辟空间,后面就不会在开辟空间了。
class A
{
public:
A(){cout<<"A"<<endl;}
};
A a;
void fn()
{
//static A b;
static int a = 0;
int b = 0;
a++;
b++;
cout<<a<<" "<<b<<endl;
}
void main()
{
cout<<"main"<<endl;
/*for(int i = 0;i<5;i++)
{
fn();
}*/
for(int i = 0;i<5;i++)
fn();
}
运行结果:
void fn()
{
//static A b;
static int a = 0;
int b = 0;
a++;
b++;
cout<<a<<" "<<b<<endl;
}
void main()
{
/*for(int i = 0;i<5;i++)
{
fn();
}*/
for(int i = 0;i<5;i++)
fn();
}