C++核心编程Day03

1 构造函数和析构函数

1.1 构造函数和析构函数基础概念
构造函数语法:
	构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数。
	Class Name(){}

析构函数语法:
	析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。
	~ClassName(){}

1.2 构造函数的分类及调用
	1.按参数类型:分为无参构造函数和有参构造函数
	2.按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test
{
public:
	Test()
	{
		cout << "构造函数!" << endl;
		this->a = 0;
		this->b = NULL;
	}

	Test(int a,const char* b)
	{
		cout << "默认有参构造!" << endl;
		this->a = a;
		this->b = (char*)malloc(sizeof(char) * (strlen(b) + 1));
		strcpy(this->b, b);
	}
	
	Test(const Test &t1)
	{
		cout << "拷贝构造函数!" << endl;
		this->a = t1.a;
	}

	~Test()
	{
		if (this->b != NULL)
		{
			free(this->b);
			this->b == NULL;
		}
		cout << "析构函数!" << endl;
	}
public:
	int a;
	char* b;
};

void test01()
{
	Test t1;
	t1.a = 1000;
	Test t2(100,"张三");

}

int main()
{
	test01();
	system("pause");
	return 0;
}
1.3 拷贝构造函数详解
	1.编译器也会提供默认的拷贝构造函数,进行成员变量的简单拷贝
	2.拷贝构造函数的形参需要使用的是引用的方式
		如果不是以引用的方式传入的话,就会导致一直死循环调用拷贝构造函数
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test
{
public:
	Test()
	{
		cout << "无参构造函数!" << endl;
		this->a = 0;
	}
	//编译器也会提供默认的拷贝构造函数,进行成员变量的简单拷贝

	//1.拷贝构造函数的形参需要使用的是引用的方式
	Test(const Test &t1)
	{
		cout << "拷贝构造函数!" << endl;
		this->a = t1.a;
	}


	~Test()
	{
		cout << "析构函数!" << endl;
	}
public:
	int a;

};

void test01()
{
	Test t2;
	//用一个已有的对象初始化另外一个对象
	Test t3(t2);

}

// 3.拷贝构造函数中形参要用引用
class Maker3
{
public:
	Maker3(int Ma)
	{
		cout << "有参构造函数" << endl;
		ma = Ma;
	}
	Maker3(const Maker3& m)
	{
		cout << "拷贝构造函数" << endl;
	}
private:
	int ma;
};

void test02()
{
	Maker3 m1(10);//调用有参构造

	Maker3 m2(m1);//调用拷贝构造

	Maker3 m3 = m1;//调用拷贝构造

	//如果拷贝构造函数中的形参不是引用
	/*
	Maker3(const Maker3 m)//const Maker3 m=m1;   const Maker3 m(m1);
	{
		cout << "拷贝构造函数" << endl;
	}
	//进行的步骤():
	1.Maker3 m2(m1);
	2.const Maker3 m=m1;
	3.const Maker3 m(m1);
	4.const Maker3 m=m1;
	5.进入死循环
	*/
}


int main()
{
	test01();
	system("pause");
	return 0;
}
1.4 匿名函数
	匿名对象(显示调用构造函数),使用匿名对象初始化判断调用哪一个构造函数,
要看匿名对象的参数类型,并且匿名函数的生命周期是当前行,当前行结束后自动销毁。
	注:不能调用拷贝构造函数去初始化匿名对象。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test
{
public:
	Test()
	{
		cout << "无参构造函数!" << endl;
	}

	Test(int a)
	{
		cout << "有参构造函数!" << endl;
	}

	Test(const Test& t1)
	{
		cout << "拷贝构造函数!" << endl;
	}

	~Test()
	{
		cout << "析构函数!" << endl;
	}
};

void test01()
{
	Test();//匿名函数,声明周期是当前行,当前行结束后自动销毁

	//cout << "test01()的销毁!" << endl;
	
	//注意:如果匿名对象有名字来接,那么就不是匿名对象了
	Test t1 = Test();
	cout << "test01()的销毁!" << endl;
}

int main()
{
	test01();
	system("pause");
	return 0;
}
1.5拷贝构造函数的调用时机
	1.对象以值传递的方式传给函数参数
	2.用一个对象初始化另一个对象
	3.函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,
	qt不调用任何构造)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test
{
public:
	Test()
	{
		cout << "无参构造函数!" << endl;
	}

	Test(int a)
	{
		cout << "有参构造函数!" << endl;
	}

	Test(const Test& t1)
	{
		cout << "拷贝构造函数!" << endl;
	}

	~Test()
	{
		cout << "析构函数!" << endl;
	}
};

void func1(Test t)
{

}
//1.类作为参数传入
void test01()
{
	Test t1;
	func1(t1);//相当于执行了Test t = t1;因此在此时执行了一次拷贝构造函数

}
//2.用一个已有的对象初始化另外一个对象
void test02()
{
	Test t1;
	Test t2(t1);
}
//3.函数的局部对象以值的方式从函数返回
Test func2()
{
	//局部对象
	Test t1;

	cout << "被调函数局部对象t1的地址:" << &t1 << endl;
	return t1;

}

void test03()
{
	Test t1 = func2();
	cout << "主调函数局部对象t1的地址:" << &t1 << endl;

}
int main()
{
	//test01();
	//test02();
	test03();

	system("pause");
	return 0;
}
1.6 构造函数调用规则
默认情况下,c++编译器至少为我们写的类增加3个函数:
	1.默认构造函数(无参,函数体为空)
	2.默认析构函数(无参,函数体为空)
	3.默认拷贝构造函数,对类中非静态成员属性简单值拷贝
注:
	1.如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
	2.如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认
	拷贝构造。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test
{
public:

	Test(int a)
	{
		cout << "有参构造函数!" << endl;
	}

	~Test()
	{
		cout << "析构函数!" << endl;
	}
};

//如果程序员提供了有参构造,那么编译器不会提供默认的无参构造函数,但是会提供默认的拷贝构造
void test01()
{
	Test t1(10);
	Test t2(1);//会提供默认的拷贝构造函数
}

class Test1
{
public:

	Test1(const Test1 &t)
	{
		cout << "拷贝构造函数!" << endl;
	}

	~Test1()
	{
		cout << "析构函数!" << endl;
	}
};

//如果程序员提供了拷贝构造函数,那么编译器不会提供默认构造函数
void test02()
{
	//Test1 t1;
}


int main()
{
	test01();

	system("pause");
	return 0;
}
1.7 多个对象的构造和析构
	1.初始化列表,是因为成员对象中的自定义数据类型的构造函数没有默认无参构造函
数,因为使用的是用户自定义的有参构造函数;初始化列表是调用成员对象的指定构造
函数。
	2.如果有多个成员对象需要进行列表初始化,那么使用,隔开。
	3.如果上面的构造函数是使用初始化列表的方式写的,那么之后的写的构造函数,
都要使用初始化列表的方式
	4.如果类有成员对象是自定义数据类型,就先调用成员对象的构造函数,再调用自身
的,析构函数相反。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test1
{
public:

	Test1()
	{
		cout << "Test1()默认无参构造函数!" << endl;
	}

	~Test1()
	{
		cout << "Test1()析构函数!" << endl;
	}
};

class Test2
{
public:

	Test2(int a)
	{
		cout << "Test2()有参构造函数!" << endl;
	}

	~Test2()
	{
		cout << "Test2()析构函数!" << endl;
	}
};

class Test3
{
public:
	//初始化列表,是因为成员对象中的自定义数据类型的构造函数没有默认无参构造函数,只有加上去的有参构造函数的时候
	//如果有多个成员对象需要进行列表初始化,那么使用,隔开
	//	Test3():t2(10),t3(10,20) or Test3(int a,int b,int c):t2(a),t3(b,c)
	Test3():t2(10)
	{
		cout << "Test3()默认无参构造函数!" << endl;
	}

	/*
	* 当想要自定义传入的数据的时候,可以写成以下的方式,这样的话就可以自定义输入数据了
	Test3(int a) :t2(a)
	{
		cout << "Test3()默认无参构造函数!" << endl;
	}
	*/

	/*
	* 如果上面的构造函数是使用初始化列表的方式写的,那么之后的写的构造函数,都要使用初始化列表的方式
	Test3(const Test3& t):t2(10)
	{
		cout << "Test3()拷贝构造函数!" << endl;
	}
	*/
	~Test3()
	{
		cout << "Test3()析构函数!" << endl;
	}
private:
	Test2 t2;
	Test1 t1;
};

//如果类有成员对象,就先调用成员对象的构造函数,再调用自身的,析构函数相反
void test01()
{
	//成员函数的构造函数调用顺序与定义相同,谁在前谁先被调用
	//如果类中的成员对象是自定义的数据类型,那么在调用之前,一定要保证类中的成员对象的构造函数和析构函数是可以访问的
	Test3 t;
}

//初始化列表是调用成员对象的指定构造函数
void test02()
{
	Test3 t;
}

int main()
{
	//test01();
	test02();


	system("pause");
	return 0;
}
1.8 深拷贝和浅拷贝
1、浅拷贝
	同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是
独立的两个对象,这种情况被称为浅拷贝.
	一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的
内存空间,析构函数做了动态内存释放的处理,会导致内存问题。

2、深拷贝
	当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定
义拷贝构造函数,自行给指针动态分配空间,深拷贝。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test1
{
public:

	Test1(int a,int b)
	{
		this->a = a;
		this->b = b;
		cout << "Test1()默认无参构造函数!" << endl;
	}

	~Test1()
	{
		cout << "Test1()析构函数!" << endl;
	}
public:
	int a;
	int b;
};

void test01()
{
	Test1 t1(1,3);
	Test1 t2(t1);
	cout << "t1::a :"<<t1.a << " t1::b :" << t1.b << endl;
	cout << "t2::a :" << t2.a << " t2::b :" << t2.b << endl;
	/*
	* 进行简单赋值 t2.a = t1.a t2.b = t1.b
	*/
}

class Test2
{
public:

	Test2(const char* a, int b)
	{
		this->a = (char*)malloc(strlen(a) + 1);
		strcpy(this->a, a);
		this->b = b;
		cout << "Test1()默认无参构造函数!" << endl;
	}
	Test2(const Test2 &t)
	{
		this->a = (char*)malloc(strlen(t.a) + 1);
		strcpy(this->a, t.a);
		this->b = t.b;
	}

	~Test2()
	{
		if (this->a != NULL) {
			free(this->a);
			this->a = NULL;
		}
		cout << "Test1()析构函数!" << endl;
	}
public:
	char* a;
	int b;
};

void test02()
{
	Test2 t1("张三", 3);
	Test2 t2(t1);
	//直接进行拷贝,会down掉,因为对一块空间进行了连续两次释放
	//解决办法,自己重新写赋值操作
	cout << "t1::a :" << t1.a << " t1::b :" << t1.b << endl;
	cout << "t2::a :" << t2.a << " t2::b :" << t2.b << endl;
	/*
	* 进行简单赋值 t2.a = t1.a t2.b = t1.b
	*/
}

int main()
{
	//test01();
	test02();

	system("pause");
	return 0;
}

2.explicit关键字

	c++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的
构造函数不能在隐式转换中使用。

[explicit注意]
	1.explicit用于修饰构造函数,防止隐式转化。
	2.是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)
而言。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test
{
public:
	//explicit关键字只能放在构造函数前面,构造函数只有一个参数或者是其他参数是默认参数有默认值
	explicit Test(int a)//防止编译器优化Test t1 = 20;这种格式
	{
		cout << "有参构造函数调用" << endl;
	}
};

int main()
{
	//Test t1 = 20;
	system("pause");
	return 0;
}

3.堆区开辟与释放

3.1 申请堆区空间和释放堆区空间
	C++中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为new
的运算符里。。当用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完
成初始化。new表达式的反面是delete表达式。delete表达式先调用析构函数,然后释
放内存。正如new表达式返回一个指向对象的指针一样,delete需要一个对象的地址。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test
{
public:
	Test()
	{
		cout << "无参构造函数调用" << endl;
	}
	Test(int a)
	{
		this->a = a;
		cout << "有参构造函数调用" << endl;
	}
	~Test()
	{
		cout << "析构函数调用" << endl;
	}

	int a;
};

void test01()
{
	//用C语言方式申请堆空间,不会调用构造函数
	Test* t = (Test*)malloc(sizeof(Test));
	//不会调用析构函数
	free(t);
}

void test02()
{
	//用new方式申请堆空间,会调用构造函数
	Test* t = new Test;
	//会调用析构函数
	delete(t);
	t = NULL;

	Test* t1 = new Test(10);
	delete(t1);
	t1 = NULL;

}


int main()
{
	//test01();
	//test02();

	system("pause");
	return 0;
}
3.2 用于数组的new和delete
	当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数,除了在栈上
可以聚合初始化,必须提供一个默认的构造函数。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test
{
public:
	Test()
	{
		cout << "无参构造函数调用" << endl;
	}
	Test(int a)
	{
		this->a = a;
		cout << "有参构造函数调用" << endl;
	}
	~Test()
	{
		cout << "析构函数调用" << endl;
	}

	int a;
};

//使用new关键字创建基础类型数组
void test03()
{
	//int* p = new int[10]{ 1,2,3,4,5,6,7,8,9,0 };//在堆区开辟10个int大小的空间,并进行赋值操作
	int* p = new int[10];
	for (int i = 0; i < 10; i++)
	{
		p[i] = i;
	}

	for (int i = 0; i < 10; i++)
	{
		cout << p[i] << endl;
	}
	//注意:在释放空间的时候,应该使用的是delete[],对开辟的数组类型的空间进行释放,即如果new时有[],那么delete的时候也要有[]
	//反之则不需要
	delete[] p;
	p = NULL;
}

//使用new关键字创建对象类型数组
void test04()
{
	Test* p = new Test[10];//这个时候初始化的话,调用的是无参构造,有些编译器通过聚合初始化的方式可以调用有参构造,但是VS不支持
	for (int i = 0; i < 10; i++)
	{
		p[i].a = i;
	}

	for (int i = 0; i < 10; i++)
	{
		cout << p[i].a << endl;
	}

	//此处如果不加[]的话,仅仅只会释放数组中的第一个Test,然后程序会down掉
	//delete p;
	delete[] p;
	p = NULL;
}

int main()
{
	//test03();
	//test04();

	system("pause");
	return 0;
}
3.3 delete void* 可能会出错
	如果对一个void*指针执行delete操作,这将可能成为一个程序错误,除非指针指向的
内容是非常简单的,因为它将不执行析构函数.以下代码未调用析构函数,导致可用内存
减少。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Test
{
public:
	Test()
	{
		cout << "无参构造函数调用" << endl;
	}
	Test(int a)
	{
		this->a = a;
		cout << "有参构造函数调用" << endl;
	}
	~Test()
	{
		cout << "析构函数调用" << endl;
	}

	int a;
};

//使用void*接收对象类型变量,可能会在最后释放的时候出现问题,不会调用析构函数
void test05()
{
	//在编译阶段,即就确认在开辟空间的时候,会调用构造函数,但是在下面释放的时候,由于对象不是自定义数据类型,因此对于析构函数此时不知道如何调用
	//上述这种编译方式叫静态联编
	void* p = new Test;

	delete p;
}

int main()
{
	test05();

	system("pause");
	return 0;
}
3.4 使用new和delete采用相同形式
	即如果出现new就使用delete,如果出现malloc才使用free。

4. 静态成员

	在类定义中,它的成员(包括成员变量和成员函数),这些成员可以用关键字static声
明为静态的,称为静态成员。不管这个类创建了多少个对象,静态成员只有一个拷贝,
这个拷贝被所有属于这个类的对象共享。
	4.1.静态成员生命周期是整个程序结束之前,但是作业域仅在类内
	4.2.静态成员变量要在类内声明,类外初始化
	4.3.静态成员变量属于类不属于对象,是所有对象共享的
class Test
{
public:
	Test() {
		cout << "无参构造函数" << endl;
	}

public:
	//1.生命周期是整个程序结束之前,但是作业域仅在类内
	static int a;
};
//2.静态成员变量要在类内声明,类外初始化
int Test::a = 10;

void test01()
{
	Test t1;
	cout << t1.a << endl;

	//3.静态成员变量属于类不属于对象,是所有对象共享的
	Test t2;
	cout << t2.a << endl;

}
	4.4.静态成员函数只能访问静态成员变量,不可以访问非静态成员变量
class Test1
{
public:
	Test1() {
		cout << "无参构造函数" << endl;
	}
	static void setA(int a)
	{
		a1 = a;
		//4.静态成员函数只能访问静态成员变量,不可以访问非静态成员变量
		//b1 = a;
		cout << "a1 = " << a1 << endl;

	}

public:
	static int a1;
	int b1;
};
int Test1::a1 = 20;

void test02()
{
	Test1 t1;
	t1.setA(50);
}

	4.5.静态成员也是有访问权限的,如果不为公共的则类外不可以访问
class Test2
{
private:
	static void setA()
	{
		cout << "a2 = " << a2 << endl;
	}

private:
	static int a2;
	int b2;
};
int Test2::a2 = 20;

void test03()
{
	Test2 t1;
	//t1.setA();
}
	4.6.const修饰的静态成员变量,最好在类内就进行初始化
class Test3
{
private:
	const static int a3 = 10;
	//也可以在类外进行初始化
	const static int b3;
};
const int Test3::b3 = 20;

void test04()
{
	Test3 t1;
	//t1.setA();
}
	4.7.普通成员函数也是可以访问静态成员变量的
总结:普通成员函数可以访问静态成员变量,但是静态成员函数不可以访问静态成员变量.

5 C++面向对象模型

5.1 成员变量和函数的存储 
在C++中,“数据”和“处理数据的操作(函数)”是分开存储的。
	1.c++中的非静态数据成员直接内含在类对象中,就像c struct一样。
	2.成员函数(member function)虽然内含在class声明之内,却不出现在对象中。
	3.每一个非内联成员函数(non-inline member function)只会诞生一份函数实例.

5.2 类成员与类大小
	1.	空类占的大小为1,因为编译器从内存上更好区分对象
class Test
{

};
void test01()
{
	//空类占的大小为1,因为编译器从内存上更好区分对象
	cout << sizeof(Test) << endl;
}
	2.类的成员函数不占用类的大小;类的静态成员变量不占用类的大小;类的静态成员
函数不占用类的大小;类的非静态成员变量会占用类的大小。
class Test1
{
public:
	void func()	//类的成员函数不占用类的大小
	{

	}
	static void func1()	//类的静态成员变量不占用类的大小
	{

	}
	static int a;	//类的静态成员函数不占用类的大小
	int b;	//类的非静态成员变量会占用类的大小
};
int Test1::a = 10;


void test02()
{
	cout << sizeof(Test1) << endl;

	//在C++中,类成员变量和成员函数是分开储存的,在调用成员函数的时候,编译器会默认传入一个指针,该指针是指向成员变量的空间,因此可以直接在成员函数中访问成员变量
}

int main()
{
	test01();
	test02();

	system("pause");
	return 0;
}
5.2 this指针

	this指针工作原理:c++规定,this指针是隐含在对象成员函数内的一种指针。当一个
对象被创建后,它的每一个成员函数都含有一个系统自动生成的隐含指针this,成员函
数通过this指针即可知道操作的是那个对象的数据。用以保存这个对象的地址,也就是
说虽然我们没有写上this指针,编译器在编译的时候也是会加上的。因此this也称为“指
向本对象的指针”,this指针并不是对象的一部分,不会影响sizeof(对象)的结果。
	5.2.1 当形参名和成员变量名相同时,用this指针区分
class Test
{
public:
	//1.当形参名和成员变量名相同时,用this指针区分
	Test(int a,int b)
	{
		this->a = a;
		this->b = b;
	}
	int a;
	int b;

};
void test01()
{
	//实例化对象的作用:1.分配空间 2.调用构造函数
	Test t1(10,20);
	t1.func();
}

int main()
{
	test01();
	
	system("pause");
	return 0;
}
	5.2.2 返回对象的本身(在运算符重载的时候对本身进行连续的++的时候会用到)
class Test
{
public:
	Test(int a,int b)
	{
		this->a = a;
		this->b = b;
	}

	void func()
	{
		cout << "a = " << this->a << " " << "b = " << this->b << endl;
	}
	//2.返回对象的本身(在运算符重载的时候对本身进行连续的++的时候会用到)
	Test& myTest()
	{
		return *this;
	}

	int a;
	int b;
};

void test01()
{
	//实例化对象的作用:1.分配空间 2.调用构造函数
	Test t1(10,20);
	t1.func();
}

int main()
{
	test01();

	system("pause");
	return 0;
}
	注:1.this指针指向的空间没有指向静态成员变量存储空间
	    2.this指针的指向是不允许修改的,因为本身是一个常量指针
	    3.实例化对象的作用:1.分配空间、 2.调用构造函数
class Test
{
public:
	Test(int a,int b)
	{
		this->a = a;
		this->b = b;
	}

	void func()
	{
		cout << "a = " << this->a << " " << "b = " << this->b << endl;
	}
	Test& myTest()
	{
		return *this;
	}
	static void func1()
	{
		//this->c = 100;//err,因为this指针指向的空间没有指向静态成员变量存储空间
	}
	int a;
	int b;
	static int c;

};
int Test::c = 10;
//注:this指针的指向是不允许修改的

void test01()
{
	//实例化对象的作用:1.分配空间 2.调用构造函数
	Test t1(10,20);
	t1.func();
}

int main()
{
	test01();

	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值