C++(十)构造函数与析构函数

一、构造函数

平常我们会定义一个变量同时初始化:int a=0, 这样这个变量在使用时就有初始值了,也就是进行初始化了。
我们在定义类(对象)时,也需要将其初始化,类是从结构体发展过来的,所以之前结构体的初始化操作,类都支持。可以在定义的时候直接初始化,也可以先定义后初始化!

或者说我想在对象定义完了之后马上将其初始化一下,这个初始化可以说是给类的成员都赋上一些初值,不想让变量默认都是一些杂乱无章的没有什么规律的未初始化状态的数。
我们可以这样做:

class student
{
public:
	int num;
	char *name;
	char sex;
	float score;
};

int main()
{
	student stu_01[3] = {
		{ 101,"Zhou ping",'M',45 },
		{ 102,"Zhang ping",'M',62 },
		{ 103,"Liou fang",'F',92.1 },
	};
	return 0;
}

上例中,我们已经将对象数组中的每个都赋初始值了,倘若有未知数个呢,用循环来做吗?非常麻烦,而且,你这种初始化的方式只能初始化public类型的成员,private类型的你是初始化不了的。于是就引出了构造函数。

构造函数:就是解决对象初始化而来的。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来主动调用它,构造函数会在对象被建立时自动被调用的。作用就是用来处理对象的初始化操作。

注意事项:
①、构造函数的名字必须与类名同名,不能随意命名,这样的话才能让编译器认为该函数是构造函数,而不是类的普通成员函数;
②、构造函数不具有任何类型,不返回任何值,连 void 都不是;
③、构造函数不需要也不应该被用户调用,它是对象建立的时候自动被系统调用,用来初始化刚刚建立的对象的;
④、如果用户没有定义自己的类的构造函数,那么系统会自动生成一个默认的构造函数,只不过该构造函数的函数体是空的,也就是什么都不做。
1.1 使用构造函数初始化对象

小示例:

#include<iostream>
#include <string>
using namespace std;

class CStudent
{
public:
	CStudent()    // 在类的内部实现构造函数
	{
		name = "";
		num = 0;
		sex = 'm';
	}
	void set_info();
	void print_info();
private:
	string name;
	char sex;
	int num;
};

void CStudent::set_info()
{
	cout << "请依次输入:姓名;学号;性别:" << endl;
	cin >> name;
	cin >> num;
	cin >> sex;
}

void CStudent::print_info()
{
	cout << "name=>sex=>num:" << endl;
	cout << name << ";" << sex << ";" << num << endl;
}

int main()
{
	CStudent stu1;
	stu1.set_info();
	stu1.print_info();
	return 0;
}
===========执行输出:===============
请依次输入:姓名;学号;性别:
zhangsan
123
m
name=>sex=>num:
zhangsan;m;123
1.2 带参数的构造函数:

Student 类不仅提供了默认的不带参数的构造函数,可以允许用户在定义类对象的时候自动调用,那么是否也可以提供一个带参数的构造函数,可以允许用户在定义类对象的时候就利用构造函数的参数来初始化对象的成员.

1.3 函数的重载

在平时的编程过程中,一个函数可能就实现一个功能,多个功能就需要多个函数。但有时候多个功能之间很像,只是一些细节有差异,那么类似这样的功能希望写成一个函数。例如,要求给出数中最大的数:
int max(int a, int b);
float max(float a, float b, float c);
上两行代码就是重载。

函数重载的要求(函数特征标不同):
重载函数的参数个数参数类型参数顺序 三者中必须至少有一种不同(不然会产生调用疑惑)。函数的返回值类型可以相同也可以不同。单纯返回值类型不同的两个同名函数不能构成函数重载,会报链接错误。

1.4 默认参数

函数默认参数的注意事项:
①、在函数声明的时候指定,如果没有函数的声明则可以放在函数的定义中,但是声明和定义只能选一个;
②、从第一个有默认值的参数开始,后面的所有参数必须都有默认值才行;(python也是这样)
③、调用的时候如果要自定义非第一个默认值的参数,那么前面所有的参数都要明确的写上默认值才行;(python也是这样)
④、从使用角度来说函数的默认值比重载更方便,从函数内部实现角度来说比函数的重载更复杂。

1.5 构造函数中的参数初始化表

用这种方式实现的构造函数体积小,代码显得更简洁

//类与构造函数的声明
class CStudent
{
public:
    char name[50];
    char sex;
    int num;
    int age;

    CStudent(char* pname, char t_sex, int t_num, int t_age);
};
//构造函数中的参数初始化表
CStudent::CStudent(char* pname, char t_sex, int t_num, int t_age) :sex(t_sex), num(t_num), age(t_age)
{
    strcpy(name, pname);
}
1.6 构造函数的四种方式
  1. 默认构造函数。以Student类为例,默认构造函数的原型为
    Student();//没有参数

  2. 初始化构造函数
    Student(int num,int age);//有参数

  3. 复制(拷贝)构造函数
    Student(Student&);//形参是本类对象的引用

    一个对象作为函数参数,以值传递的方式传入函数体;
    一个对象作为函数返回值,以值传递的方式从函数返回;
    一个对象用于给另外一个对象进行初始化(常称为赋值初始化);直接使用一个对象对另一个对象初始化需要重载运算符,否则析构时容易报错

  4. 转换构造函数
    Student(int r); //形参是其他类型变量,且只有一个形参
    当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数,隐式转换为本类对象。

二、析构函数

析构函数也是一个在类中跟构造函数类似的特殊功能的成员函数。只不过它的作用是与构造函数相反,是在对象的生命周期结束的时候会被自动调用的。析构函数的名字跟类名相同,并且前面带上一个取反的符号~,表达的意思也就是跟构造函数的过程相反。

默认情况下,如果类的设计者没有自己定义析构函数,那么编译器会自动为该类生成一个默认的析构函数,只不过函数体是空的,也就是什么都没做。所以,如果需要在对象被删除的时候做一些操作的话,那么就得自己定义析构函数。

以下几种情况会自动调用析构函数:
①、如果在一个函数中定义了一个局部变量的对象,那么当这个函数执行结束时也就是该变量对象生命周期结束的时候,所以析构函数会被自动调用;
②、全局变量或者static类型的变量,他们的生命周期一般是在程序退出的时候,这时候该对象的析构函数才会被调用;
③、如果是用new操作符动态的创建了一个对象,只有当用delete进行释放该对象的时候,析构函数才会被调用;
2.1 析构函数的作用

构造函数不是新建对象,而是在对象被创建出来之后自动被调用,用来初始化相关信息的函数。同理,析构函数也不是用来删除对象的,而是当对象被删除的时候自动会被调用的,用来做一些对象被删除之前的清理工作。只要对象的生命周期结束,那么程序就会自动执行析构函数来完成这个工作的。
析构函数不返回任何值,没有函数类型,也没有任何函数的参数。由于上面这些特点,所以析构函数不能被重载,所以说一个类可以有多个构造函数,但只能有一个析构函数。

2.2 整篇文章小例子:
  • 头文件,声明(类、构造函数、析构函数)
#pragma once
#pragma warning(disable:4996)
#include <iostream>
using namespace std;

class C_Student
{
private:
	char * name;
	int len;
	static int num_strings;

public:
	C_Student();
	C_Student(const char * s);
	~C_Student();

	friend ostream & operator << (ostream & os, const C_Student & st);
};
  • 实现函数(函数的具体实现)
// gouzao_hanshu_01.cpp : 定义控制台应用程序的入口点。
//
#pragma warning(disable:4996)
#include "stdafx.h"
#include <cstring>
#include "Student_h.h"
using namespace std;

int C_Student::num_strings = 0;


C_Student::C_Student()
{
	len = 10;
	name = new char[10];
	strcpy(name, "zhang");
	num_strings++;
	cout << "num_string_null:" << num_strings << endl;
}


C_Student::C_Student(const char * s)
{
	len = strlen(s);
	name = new char[len + 1];
	strcpy(name, s);
	num_strings++;
	cout << "num_string_dong:" << num_strings << endl;
}


C_Student::~C_Student()
{
	--num_strings;
	delete [] name;         //使用new [] 申请内存,必须使用delete [] 释放内存
	cout << "函数析构完成:" << num_strings << endl;
}

ostream & operator << (ostream & os, const C_Student & st)
{
	os << st.name;
	return os;
}
  • 主函数(实例化对象)
#pragma warning(disable:4996)
#include "stdafx.h"
#include <cstring>
#include <iostream>
#include "Student_h.h"
using namespace std;




int main()
{
	cout << "======实例化:======" << endl;
	C_Student stu01("wawawa");
	C_Student stu02("hahaha");
	cout << "======stu01 实例化:======" << stu01 << endl;
	cout << "======stu02 实例化:======" << stu02 << endl;
	return 0;
}


====================运行输出=====================
======实例化:======
num_string_dong:1
num_string_dong:2
======stu01 实例化:======wawawa
======stu02 实例化:======hahaha
函数析构完成:1
函数析构完成:0

其实上述代码有一些小问题。或者说是考虑不周之处。就是对对象进行复制初始化(构造)会出现析构报错。

报错与解决
严重性代码说明项目文件 行错误 C4996 ‘strcpy’: This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. Test f:\cworkspace\test\test\main.c 30
用VS编译代码是遇到如上问题,vs准备弃用strcpy的,安全性较低,所以微软提供了strcpy_s来代替,如果想继续使用strcpy的,main前面加上

#pragma warning(disable:4996)
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页