类的构造函数和析构函数、默认构造函数

article/2025/10/2 6:03:25

前言

程序只能通过成员函数来访问数据成员,因此需要设计合适成员函数,才能成功地将对象初始化。

类构造函数专门用于构造新对象,将值赋给他们的数据成员,进行初始化。

构造函数名称与类名相同,没有返回值,没有被声明为void类型。

声明和定义构造函数

构造函数:专门用于构造新对象、将值赋给它们的数据成员。在创建类对象时被调用。

构造函数特征:
● 构造函数的名称和类名相同
● 构造函数可以重载
● 构造函数没有声明类型

由于Stock对象提供3个值,因此应为构造函数提供3个参数。

Stock(const string &co, long n = 0, double pr = 0.0)

第一个参数:指向字符串的指针,用于初始化成员company。n和pr参数为shares和share_val成员提供值。

注意:没有返回值,原型位于类声明的公共部分。

Stock::Stock(const string &co, long n, double pr)
{company = co;if(n < 0) {cout << "Number of shares can't be negative;"<< company << " shares set to 0.\n";shares = 0;}elseshares = 0;share_val = pr;set_tot();
}

上述代码与acquire()函数相同,区别在于,程序声明对象时,将自动调用构造函数。

为了避免函数参数名与成员名相同,在数据成员名中使用m_前缀:m_company

使用构造函数

● 显示调用构造函数

Stock food = Stock("World Cabbage", 250, 1.25);

这将food对象的company成员设置为字符串"World Cabbage",将shares成员设置为250。

● 隐式调用构造函数

Stock garment("Furry Mason", 50, 2.5);

每次创建类对象(甚至使用new动态分配内存)时,都会使用构造函数。

● 构造函数与new

Stock *pstock = new Stock("Electroshock Games", 18, 19.0);

创建一个Stock对象,将其初始化为参数提供的值,并将该对象的地址赋给pstock指针。这种情况下,对象没有名称,但可以使用指针来管理该对象。

无法使用对象来调用构造函数,因为在构造函数构造对象之前,对象是不存在的。构造函数是用来创建对象,不能通过对象来调用。

默认构造函数

默认构造函数是在未提供显式初始值时,用来创建对象的构造函数,默认构造函数没有参数,因为声明中不包含值。

Stock fluffy_the_cat;

注意:如果没有提供任何构造函数,则C++将自动提供默认构造函数。它是默认构造函数的隐式版本,不做任何工作。

对于Stock类来说,默认构造函数可能如下:

Stock::Stock() {}

因此将创建fluffy_the_cat对象,但不初始化其成员,与int x;一样,没有提供值给它。

默认构造函数没有参数,因为声明中不包含值。

当且仅当没有定义任何构造函数,编译器才会提供默认构造函数。如果为类定义了构造函数,则程序员必须为它提供默认构造函数。

如果提供了非默认构造函数,但没有提供默认构造函数,则Stock stock1;的声明将出错。这样做的原因可能是想禁止创建未初始化的对象。然而,如果要创建对象,而不显式地初始化,则必须定义一个不接受任何参数的默认构造函数。

定义默认构造函数:
● 一种是给已有构造函数的所有参数提供默认值。

Stock(const string &co = "Error", int n = 0, double pr = 0.0);

● 通过函数重载来定义另一个构造函数,即一个没有参数的构造函数。

Stock();

注意:默认构造函数只能有一个,通常使用第一种来作为默认构造函数。实际上,应初始化所有对象,以确保所有成员一开始就有已知的合理值。因此,用户定义的默认构造函数通常给所有成员提供隐式初始化值。

Stock::Stock() //默认构造函数
{company = "no name";shares = 0;shares_val = 0.0;total_val = 0.0;
}

提示:通常应提供对所有类成员做隐式初始化的默认构造函数。

使用上述任何一种方式,创建了默认构造函数后,就可以声明对象,而不对它们进行显示初始化。

Stock first; //隐式调用默认构造函数
Stock first = Stock(); //显式
Stock *prelief = new Stock; //隐式

然而,不要被非默认构造函数的隐式所误导:

Stock first("Concrete Conglomerate"); //构造函数
Stock second(); //一个函数
Stock third; //默认构造函数

一个声明调用非默认构造函数,即接受参数的构造函数;第二个声明指出,second()是一个返回Stock对象的函数。
第三个是隐式地调用默认构造函数,不要使用圆括号。

析构函数

析构函数:用构造函数创建对象后,程序负责跟踪该对象,直到过期为止,对象过期时,程序将自动调用一个特殊的成员函数,即析构函数。

如果构造函数使用new来分配,则析构函数将使用delete来释放内存。

如果没有使用new,只需让编译器生成一个什么都不做的隐式析构函数。

声明析构函数,在类名前加上~:

~Stock();

析构函数没有参数,没有返回值,没有声明类型。

每个类都只能有一个析构函数。

由于Stock的析构函数不承担重要的工作,因此可以将它编写为不执行任何操作的函数。

Stock::~Stock()
{
}

析构函数的调用:
● 如果创建的是静态存储类对象,则其析构函数将在程序结束时自动被调用
● 如果创建的是自动存储类对象,则其析构函数将在程序执行完代码块时自动被调用
● 如果对象是通过new创建的,则它将驻留在栈内存或者自由存储区,当使用delete来释放内存时,则其析构函数将自动被调用。
● 程序可以创建临时对象来完成特定的操作,在这种情况下,程序将在结束对该对象的使用时自动调用其析构函数。
● 在类对象过期时析构函数将自动调用,因此必须有一析构函数。
● 如果程序员没有提供析构函数,编译器会隐式声明一个默认析构函数,并发现导致对象被删除的代码后,提供默认析构函数。
● 最先创建的对象将最后删除,即最后调用析构函数

改进Stock类

加入了构造函数和析构函数。

stock10.h

加入将构造函数和析构函数的原型,删除了acquire()函数,#ifndef技术来防止多重包含。

//stock10.h
#ifndef STOCK10_H
#define STOCK10_H
#include <string>
using namespace std;class Stock
{
private:string company;long shares;double share_val;double total_val;void set_tot(){ total_val = shares * share_val; }
public:Stock(); //默认构造函数Stock(const string &co, long n = 0, double pr = 0.0);~Stock(); //析构函数void buy(long num, double price);void sell(long num, double price);void update(double price);void show();
};#endif

stock10.cpp

添加构造函数和析构函数定义,观察何时被调用。

//stock10.cpp
#include <iostream>
#include "stock10.h"//默认构造函数
Stock::Stock()
{cout << "Default constructor called\n";company = "no name";shares = 0;share_val = 0.0;total_val = 0.0;
}//构造函数
Stock::Stock(const string &co, long n, double pr)
{cout << "Constructor using" << co << " called\n";company = co;if(n < 0){cout << "Number of shares can't be negative; "<< company << " shares set to 0.\n";shares = 0;}elseshares = n;share_val = pr;set_tot();
}//析构函数
Stock::~Stock()
{cout << "Bye, " << company << "!\n";
}void Stock::buy(long num, double price)
{if(num < 0){cout << "Number of shares purchased can't be negative. "<< "Transaction is aborted.\n";}else{shares += num;share_val = price;set_tot();}
}void Stock::sell(long num, double price)
{if(num < 0){cout << "Number of shares sold can't be negative. "<< "Transaction is aborted.\n";}else if(num > shares){cout << "You can't sell more than you have! "<< "Transaction is aborted.\n";}else{shares -= num;share_val = price;set_tot();}
}void Stock::update(double price)
{share_val = price;set_tot();
}void Stock::show()
{//设置格式#.###ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield);streamsize prec = cout.precision(3);cout << "Company: " << company<< " Shares: " << shares << '\n';cout << " Share Price: $" << share_val;//设置格式#.##cout.precision(2);cout << " Total Worth: $" << total_val << '\n';//重置原格式cout.setf(orig, ios_base::floatfield);cout.precision(prec);
}

usestok1.cpp

//usestok1.cpp
#include <iostream>
#include "stock10.h"int main()
{//添加这两大括号,两个析构函数调用将在到达返回语句前执行//如果没有这些大括号,将执行完整个main()后,才调用析构函数,即在两个析构函数调用前关闭则无法看到最后两条消息{cout << "Using constructors to create new objects\n";Stock stock1("NanoSmart", 12, 20.0); stock1.show();Stock stock2 = Stock("Boffo Objects", 2, 2.0);stock2.show();cout << "Assigning stock1 to stock2:\n";//将一个对象赋给同类型的另一个对象,其源对象的每个数据成员的内容复制到目标对象中相应的数据成员中stock2 = stock1; cout << "Listing stock1 and stock2:\n";stock1.show();stock2.show();cout << "Using a constructor to reset an object\n";//将新值赋给它,构造函数创建一个新的、临时的对象,然后赋值给stock1.随后程序调用析构函数,以删除该临时对象stock1 = Stock("Nifty Foods", 10, 50.0);cout << "Revised stock1:\n";stock1.show();cout << "Done\n";}return 0;
}

程序说明

创建一个名为stock1的Stock对象,并将其数据成员初始化为指定值。

Stock stock1("NanoSmart", 12, 20.0); 

输出内容:

Constructor using NanoSmart called
Company: NanoSamrt Shares: 12

使用另一种语法创建并初始化一个名为stock2的对象:

Stock stock2 = Stock("Boffo Objects", 2, 2.0);

C++允许编译器使用两种方式来执行第二种语法:
● 一种是和第一种语法完全相同。

Constructor using Boffo Objects called
Company: Boffo Objects Shares: 2

● 另一种是允许调用构造函数来创建一个临时对象,然后将该临时对象复制到stock2中,并丢弃它。则将为临时对象调用析构函数:

Constructor using Boffo Objects called
Bye, Boffo Objects!
Company: Boffo Objects Shares: 2

生成上述输出的编辑器可能立刻删除临时对象,但也可能会等一段时间,在这种情况下,析构函数的消息将会过一段时间显示。

可以将一个对象赋给同类型的另一个对象:

stock2 = stock1;

给类对象赋值时,将把一个对象的成员复制给另一个,对象的每个数据成员的内容复制到目标对象中相应的数据成员中。

stock1对象已经存在,不是对stock1进行初始化,而是将新值赋给它。

stock1 = Stock("Nifty Foods", 10, 50.0);

让构造函数创建一个新的、临时的对象,然后将其内容复制给stock1来实现的。随后调用析构函数,以删除该临时对象。

Using a constructor to reset an object
Constructor using Nifty Foods called //创建临时对象
Bye, Nifty Food; //销毁临时对象
Revised stock1;
Company: Nifty Foods Shares: 10 //数据已经复制给stock1Share Price: $50.00 Total Worth: $500.00

main()函数结束时,其局部变量(stock1和stock2)将消失,由于这种自动变量被放在栈中,因此最后创建的对象将最先被删除,最先创建的对象将最后被删除(“NanoSmart"最初位于stock1中,但随后被传输到stock2中,然后stock1被重置为"Nifty Food”)。

Done
Bye, NanoSmart!
Bye, Nifty Foods!

下面两条语句差别:

Stock stock2 = Stock("Boffo Objects", 2, 2.0);
stock1 = Stock("Nifty Foods", 10, 50.0); //临时对象

第一条是初始化,它创建有指定值的对象,可能会创建临时对象(也有可能不会)。第二条是赋值,像这样在赋值语句中使用构造函数总会导致在赋值前创建一个临时对象。

提示:一般采用初始化方式,效率更高。
在这里插入图片描述

C++11初始化列表

提供与某个构造函数的参数列表匹配的内容,并用大括号将它们括起来:

Stock hot_tip = {"Derivatives Plus Plus", 100, 45.0};
Stock jock {"Sport Age Storage, Inc"};
Stock temp {};

用大括号括起的列表与下面的构造函数匹配:

Stock::Stock(const string &co, long n = 0, double pr = 0.0);

因此,将使用该构造函数来创建这两个对象。创建对象jock时,第二和第三个参数将默认值为0和0.0。第三个声明与默认构造函数匹配,因此将使用构造函数创建对象temp。

const成员函数

const Stock land = Stock("Kludgehorn Properies");
land.show();

编译器将拒绝第二行,因为show()的代码无法确保调用对象不被修改——调用对象和const一样,不应被修改。

我们以前通过将函数参数声明为const引用或者指向const的指针来解决这种问题。但这里存在语法问题:show()方法没有任何参数。相反,它所使用的对象是由方法调用隐式提供的。

一种新的语法:保证函数不会修改调用对象。C++解决方式是将const放在函数的括号后面。

void show() const; //保证函数不会修改调用对象

同样函数定义的开头:

void stock::show() const;

以这种方式声明和定义的类函数被称为const成员函数。像const引用和指针作用函数形参一样,只要类方法不修改调用对象,就应将其声明为const。

构造函数和析构函数小结

构造函数:
● 一种特殊的函数。
● 在创建类对象时被调用。
● 构造函数名称和类名相同
● 构造函数可以重载,创建多个同名构造函数,每个函数的参数列表都不同。
● 构造函数没有声明类型。
● 构造函数用于初始化类对象的成员,初始化应与构造函数的参数列表匹配。

例如,Bozo类的构造函数的原型:

Bozo(const char* fname, const char* lname);

初始化新对象:

Bozo bozetta = bozo("Bozetta", "Biggens"); //初级形式
Bozo fufu("Fufu", "O'Dweeb"); //简写
Bozo *pc = new Bozo("Popo", "Le Peu"); //动态对象

C++11初始化列表:

Bozo bozetta = {"Bozetta", "Biggens"}; 
Bozo fufu{"Fufu", "O'Dweeb"}; 
Bozo *pc = new Bozo{"Popo", "Le Peu"}; 

如果构造函数只有一个参数,则将对象初始化为一个与参数的类型相同的值时,该构造函数将被调用。

Bozo dribble = bozo(44); //第一种形式
Bozo roon(66); //第二种形式
Bozo tubby = 32; //一个参数特殊形式

警告:接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值:

Classname object = value;

默认构造函数:
● 默认构造函数没有参数
● 如果创建对象时没有进行显式初始化,则将调用默认构造函数。
● 如果程序中没有提供任何构造函数,则编译器会为程序定义一个默认构造函数;否则,必须自己提供默认构造函数。
● 默认构造函数没有参数,如果有,必须给所有参数提供默认值

Bozo(); //默认构造函数原型
Bistro(const char *s = "Chez Zero"); //默认值

对于未被初始化的对象,程序将使用默认构造函数来创建:

Bozo bubi;
Bozo *pb = new Bozo;

析构函数
● 就像对象被创建时程序将调用构造函数一样,当对象被删除时,程序将调用析构函数。
● 每个类都只能有一个析构函数。
● 析构函数没有返回类型(连void都没有),也没有参数,其名称为类名称前加上~。

~Bozo();

如果构造函数使用了new,则必须提供使用delete的析构函数。


http://chatgpt.dhexx.cn/article/ccOtWnmC.shtml

相关文章

c++中的构造函数和析构函数

类和对象中&#xff0c;包括构造函数和析构函数&#xff0c;比较重要&#xff0c;通过学习总结一下&#xff0c;以便以后可以回顾&#xff01; 目录 构造函数 1.默认构造函数 2.有参构造函数 3.委托构造函数 4.复制(拷贝)构造函数 5.移动构造函数 左值引用与右值引用 析…

C++类构造函数和析构函数

11.3 类构造函数和析构函数 构造函数&#xff1a;是为了在定义对象时自动初始化其成员变量的值。 构造函数没有返回值&#xff0c;也没有被声明为void类型&#xff1b;因此&#xff0c;构造函数没有声明类型。 11.3.1 声明和定义一个构造函数 构造函数原型&#xff1a;在这…

C++ 构造函数和析构函数可以是虚函数嘛?

简单总结就是&#xff1a;构造函数不可以是虚函数&#xff0c;而析构函数可以且常常是虚函数。 构造函数不能是虚函数 1. 从vptr角度解释 虚函数的调用是通过虚函数表来查找的&#xff0c;而虚函数表由类的实例化对象的vptr指针(vptr可以参考C的虚函数表指针vptr)指向&#x…

构造函数和析构函数顺序

父子类 1、构造顺序&#xff1a; 创建一个子类对象&#xff0c;则父类、子类的构造方法都执行&#xff0c;且是 先父类 构造方法&#xff0c;再子类构造方法 派生类的构造顺序&#xff1a;先父类&#xff0c;后子类&#xff0c;因为子类很有可能会用到从父类继承来的成员 2、析…

【C++】构造函数与析构函数

1. 概述 构造函数&#xff1a;用于初始化对象&#xff0c;没有返回值&#xff0c;函数名和类名相同&#xff0c;只有在对象初始化的时候才会被调用。构造函数的分类&#xff1a; 默认构造函数&#xff1a;是编译器自动生成&#xff0c;没有任何参数的构造函数。 有参构造函数&…

何时调用构造函数和析构函数

何时调用构造函数和析构函数 构造函数的作用是保证每个对象的数据成员都有何时的初始值。 析构函数的作用是回收内存和资源&#xff0c;通常用于释放在构造函数或对象生命期内获取的资源。 一般我们都知道构造和析构的次序&#xff1a; 构造从类层次的最根处开始&#xff0c…

C++篇----构造函数和析构函数

在很多时候&#xff0c;当写了初始化&#xff0c;动态开辟的&#xff0c;需要写销毁函数&#xff0c;写了销毁函数之后&#xff0c;但是却忘记了调用这些函数&#xff0c;忘记调用初始化函数还好&#xff0c;编译器会报错&#xff0c;但是如果是忘记调用销毁函数&#xff0c;那…

c++构造函数和析构函数

一、构造函数和析构函数的特点 构造函数和析构函数是一种特殊的公有成员函数&#xff0c;每一个类都有一个默认的构造函数和析构函数&#xff1b;构造函数在类定义时由系统自动调用&#xff0c;析构函数在类被销毁时由系统自动调用&#xff1b;构造函数的名称和类名相同&#…

构造函数与析构函数

一&#xff0c;引言 由于c语言常常会忘记初始化与销毁&#xff0c;造成许多麻烦。所以c就引入了构造函数与析构函数&#xff0c;分别用来完成初始化与清理工作&#xff0c;且由编译器自动调用&#xff0c;这就避免了许多麻烦。 二&#xff0c;构造函数 构造函数是一个特殊的成…

构造函数和析构函数

文章目录 前言 1.构造函数<1>概念<2>特性 2.初始化列表<1>概念<2>特征 3.析构函数<1>概念<2.>特征 前言 如不清楚类的定义可以点击此篇文章&#xff1a;类的定义与引入 C为很么要引入构造函数和析构函数呢&#xff0c;前文讲到大佬引入了…

C++ 构造函数和析构函数 详解

目录 概述构造函数的分类1. 无参(默认)构造函数2. 有参构造函数3. 委托构造函数4. 复制(拷贝)构造函数5. 移动构造函数 构造函数调用规则析构函数 概述 C中用构造函数和析构函数来初始化和清理对象&#xff0c;这两个函数将会被编译器自动调用。对象的初始化和清理是非常重要的…

java异常处理及自定义异常

异常处理的实际上就是&#xff1a; 有风险的行为&#xff08;方法&#xff09;可能会将异常抛出&#xff08;throws&#xff09;。调用该方法的程序会尝试&#xff08;try&#xff09;去运行,运行的同时捕捉&#xff08;catch&#xff09;异常。 简而言之&#xff0c;就是对有…

java异常 — — 自定义异常

三、自定义异常 3.1、概述 为什么需要自定义异常类: Java中不同的异常类分别表示看某一种具体的异常情况&#xff0c;那么在开发中总是有些异常情况是SUN没有定义好的此时我们根据自己业务的异常情况来定义异常类。例如年龄负数问题&#xff0c;考试成绩负数问题等等。 在上…

JAVA自定义异常处理

自定义异常处理可以分为两种&#xff0c;一种是自定义编译处理&#xff0c;另一种是自定义运行处理 1.自定义编译处理需要创建一个异常类用于继承Exception类 重写构造器 在出现异常的地方用throw new 自定义对象抛出 作用&#xff1a;编译时异常时编译阶段就报错&#xff…

Java的自定义异常类

Java的异常处理机制可以让程序具有极好的容错性&#xff0c;让程序更加健壮。当程序运行出 现意外情形时&#xff0c;系统会自动生成一个 Exception对象来通知程序&#xff0c;从而实现将“业务功 能实现代码”和“错误处理代码”分离&#xff0c;提供更好的可读性。 Java把所…

Java自定义异常及统一处理,信息返回

开始操作 创建enums&#xff0c;exception包&#xff1a; enums包下&#xff1a; 创建BaseCodeEnum接口 创建Response类&#xff1a;为统一信息返回类 创建ResponseCode枚举类&#xff1a;在这里定义我们需要的异常 exception包下&#xff1a; 创建HandlerException类&#…

Java自定义异常类统一处理异常

当程序发生异常时&#xff0c;会返回一大堆不友好的内容&#xff0c;非常不美观&#xff01; 我们在写代码的时候&#xff0c;对异常处理一般是try catch或者抛出异常throws Exception。 try catch大家都知道&#xff0c;代码中大量的try catch会占用内存影响性能&#xff0c…

Java中的自定义异常

代码实现 自定义异常类型主要实现代码 public class Exception_demo extends Exception{//自定义异常&#xff0c;需要把自定义异常类继承于Exception异常类&#xff0c;自定义异常类属于异常类的子类public Exception_demo(){//构造方法也叫做构造器&#xff0c;构造方法的名…

【Java异常】自定义异常

Java中定义了大量的异常类&#xff0c;虽然这些异常类可以描述编程时出现的大部分异常情况&#xff0c;但是在程序开发中有时可能需要描述程序中特有的异常情况,例如在设计divide()方法时不允许被除数为负数。为了解决这样的问题,Java允许用户自定义异常&#xff0c;但自定义的…

JAVA项目中自定义异常

JAVA项目中自定义异常 1.数据返回处理类 Data public class R<T> implements Serializable {private static final long serialVersionUID -8497670085742879369L;ApiModelProperty(value "返回码", example "200")private Integer code200;Api…