#define _CRT_SECURE_NO_WARNINGS//一定要加在最前面
#include <iostream>
using namespace std;
//对象谁先构造谁后析构
class Teacher
{
public:
Teacher(int id, const char *name)
{
my_id = id;
int len = strlen(name);//这是计算name指向区域的字符串的长度,但不包含\n
my_name = (char *)malloc(len + 1);
strcpy(my_name, name);
}
//1.调用默认拷贝构造函数创建对象时,即为浅拷贝
//2.调用显式拷贝构造函数创建对象时,即为深拷贝
//在类的私有变量中有指针类型的变量或析构函数中调用free函数释放对象外部空间时,需要进行深拷贝,即在类中定义显式拷贝构造函数,防止发生段错误,访问非法内存
Teacher(const Teacher &another)
{
my_id = another.my_id;
int len = strlen(another.my_name);
my_name = (char *)malloc(len + 1);
strcpy(my_name, another.my_name);
}
~Teacher()
{
if (my_name != NULL)
{
free(my_name);
}
}
private:
int my_id;
char *my_name;//char *name与char name[64]是完全不一样的,虽然都可以指代字符串,char *name是一个指针,字符串不在其内部,char name[64]是一个数组,字符串存储在其内部
};
struct Test
{
int a;
int b;
};
class Animal
{
public:
//构造函数初始化列表,当类里有别的类的对象时,这个类的构造函数想要初始化这个类里别的类的对象时需要用下面一段代码的格式
//即类里有对象时,构造函数应写成构造函数列表形式
//这是调用Teacher类的拷贝构造函数,来初始化对象my_t1,my_t2
Animal(Teacher &t1, Teacher &t2, int kind, Test &test) : my_t1(t1), my_t2(t2)//:my_t1(t1), my_t2(t2)冒号后面的才是表示调用的是拷贝构造函数,前面函数参数里只是表示后面的可以调用函数参数里面的变量而已
{
//Teacher my_t1 = t1;在别的类内部这种写法是错误的,这是重新定义一个变量my_t1了,不再是类Animal的私有变量my_t1
}
//一个类里定义了多个对象,这些对象的初始化与构造函数初始化列表位置前后没有任何关系,只与类里定义的顺序有关系,谁先定义谁先构造,如Animal类里先定义了my_t1,后定义了my_t2,则构造函数初始化列表先构造my_t1,后构造my_t2
//这是调用Teacher类的显式构造函数,来初始化对象my_t1,my_t2,这与上一个虽同时构造函数初始化列表
Animal(int t1, char *t2, int b) : my_t1(t1, t2), my_t2(t1, t2)//:my_t1(t1, t2), my_t2(t1, t2)冒号后面的才是表示调用的是构造函数,前面函数参数里只是表示后面的可以调用函数参数里面的变量而已
{
}
/*
Animal(Test &test)
{
my_test = test;//Test类型为结构体,(不存在构造函数???),所以可以引用来直接赋值
}*/
private:
int my_kind;
Teacher my_t1;
Teacher my_t2;
Test my_test;
};
//无参构造函数
class Student1
{
public:
Student1()
{
my_age = 10;
}
private:
int my_age;
};
//有参构造函数
class Student2
{
public:
Student2(int age)
{
my_age = age;
}
private:
int my_age;
};
class Ball1
{
public:
private:
int my_volume;
Student1 my_s;//类1里有私有类2的对象时,类2有默认构造函数或者无参构造函数时,类1可以直接用默认构造函数,不需要构造函数初始化列表
};
//初始化列表本质上就是对类里别的类的对象进行怎样初始化的一种规定
//1.首先这个别的类的对象是用构造函数构造还是拷贝构造函数构造
//2.使用构造函数构造时,默认构造函数和无参构造函数不需要构造函数初始化列表,有参构造函数需要,且要区分用哪个有参构造函数进行构造,形式参数的值是多少,这样才能确定这个别的类的对象初始化成什么值
//3.如果使用拷贝构造函数时,则一定需要初始化列表,确定拷贝的值是什么,进而确定别的类的对象初始化成什么值
class Ball2
{
public:
//构造函数初始化列表是一种构造函数的形式,因为构造函数可以重载,所以构造函数初始化列表是有多种的
//这种写法是构造函数初始化列表调用类Student2的拷贝构造函数进行初始化类里的私有对象my_s的
Ball2(Student2 &s) : my_s(s), m_m(10)//注意构造函数初始化列表形式为my_s(s)两个都是对象,不是my_s = s这种写法
{
my_volume = 10;
}
//这种写法是构造函数初始化列表调用类Student2的有参构造函数进行初始化类里的私有对象my_s的
Ball2(int &a) : my_s(a), m_m(10)//类中const修饰的常量需要在构造函数里初始化,且只能在函数初始化列表里进行初始化,其他都是错误的
{
my_volume = 10;
}
private:
//在类里的变量不可以赋值,如果需要赋值,请在构造函数中赋值,就算是const常量也不可以赋值(虽然const修饰的常量一定要初始化时赋值,但这只是个类,并不是对象,还没有开辟空间,所以是可行的)
int my_volume;
Student2 my_s;//类1里有私有类2的对象时,类2没有默认构造函数或者无参构造函数时,即类2含有有参构造函数,类1不可以直接用默认构造函数,需要定义构造函数初始化列表
const int m_m;
};
class Function
{
public:
//在构造函数中调用构造函数是一个非常危险的行为,而且没人任何意义,所以尽量避免
//如果非要在构造函数中调用构造函数,一种情况是形成递归且没有递归出口,导致堆栈溢出,第二种情况会创建一个匿名对象,但很快就会被释放掉
Function(int a)
{
my_a = a;
Function(a, 100);//创建了一个匿名对象,是另外一个对象
//...这个匿名对象会在这个位置被立即释放掉,并没有任何意义
cout << "first my_b" << my_b << endl;
}
Function(int a, int b)
{
my_a = a;
my_b = b;
cout << "second my_b" << my_b << endl;
}
void Print()
{
cout << "third my_a=" << my_b << endl;
}
private:
int my_a;
int my_b;
};
//调用free函数只是把指针指向的内存空间释放了,系统可以重新调用这块内存空间,但指针并没有赋值为NULL,指针扔指向这块内存空间
int main()
{
//Teacher t1(10, "hehe");这种写法是错误的,因为第二个参数为"hehe"为常量字符串,不可更改,与类里构造函数参数完全不一致
char name[64] = "hehe";
Teacher t1(10, name);
//调用类里的默认拷贝构造函数,其结果会报段错误,因为对象t2里的my_name指针与对象t1里的my_name指针指向的是同一个空间,对象t2被回收时调用析构函数释放了一次空间,t1再被回收又会调用析构函数在释放一次相同空间
Teacher t2(t1);
//结构体初始化赋值
Test test = { 1,1 };
Animal animal(t1, t2, 10, test);
Ball1 ball1;
Student2 s(10);
Ball2 ball2(s);//或者可以写成Ball2 ball2(2),等价于Ball2 ball2 = s;这就相当于用s给对象ball2初始化赋值,虽然对象s与对象ball2类型不一样,但ball2中含有对象s的类型变量,即ball2包含s,所以可以直接初始化赋值
int a = 10;
Ball2 ball3(a);
Function func(10);
func.Print();
return 0;
}