构造函数:
C++的构造的主要作用:
在 类对象创建的时候通过构造函数对类对象进行初始化。即:为对象成员变量 进行赋值操作,一个类可以有多个构造函数,来满足初始化时的需求:即构造函数 的重载。重载的函数之间通过 函数参数和参数类型进行区分。
C++构造函数的特点:
1.构造函数的名字统一是类名。所以我们要注意的是:CStack() 指的是CStack类的构造函数,而CStack 指的是类。
2.构造函数为类对象在创建时提供初始化。即:为成员变量指定赋值。它没有返回值也没有函数类型的修饰。
3.构造函数不能够被直接调用,通常是在类对象创建时自行调用。即:程序执行到类对象创建时进行。
4.默认的构造函数是公有的,但是如果放在私有里面,那么我们就不可以通过new 运算符来创建对象。当一个类没有创建构造函数的时候,编译器会自行创建 一个什么都不敢的构造函数。
声明格式:
<类名 > (参数)
类的构造函数和常规初始化语法的区别:
int year = 2001;
struct thing
{
char *pn;
int m;
};
thing amabob = {"wrong", 20}; //可行的,因为struct结构体内声明的成员变量 是默认为公有的,外部可以直接访问
CStack hot = {“Sukic”,200,50.25}; //这里CStack为我们已经定义好的类,在程序中想要创建一个hot对象,但是这样是不可以的,因为CStack 的成员变量 //是私有的,不能 直接在外部访问,这也是为什么存在构造函数的原因之一。
现在我们假设: 如果没有构造函数的话:
假设:
CStack 有一个m_sum的int 类型成员变量,当我们实例化一个对象:CStack tack; 我们并不知道m_sum 的值,如果要对m_num进行运算等操作的话,首先要让m_sum有一个初始值,那么我们可以写一个函数来给m_sum赋值:Adele(num)m_num = num;那么我们在实例化对象之后需要 执行Adele函数:
CStack tack;
tack.Adele(1001);//让m_num 为1001;
那么这样看来,每次我要实例化一个对象的时候,都要在后面调用一下赋值函数,这样显然是非常麻烦的。所以构造函数产生了。这个函数的功能跟Adele 的功能是类似的,只是与Adele比起来在名字和返回值等方面有区别。而且程序声明对象时,将自动执行构造函数。
因此C++提供 了自己的机制:类中通过默认构造函数提供赋值的功能。这样在实例化对象之时,自行给对象的成员变量赋值。当然,如果想要自己写构造函数也是可以的。只需要在实例化的时候将参数传到重载的构造函数中去就可以了。
声明和定义构造函数:
例子:
现在我们有一个CStack类,有成员变量:string m_name,int m_age.
那么我们要对m_name 和m_age进行初始化时的构造函数需要两个参数:
CStack::CStack(const string &name,const int & age)
{
m_name = name;
m_age = age;
}
注意事项:
在上面的代码中m_name 和m_age 是成员变量,但是如果参数名和成员变量名相同的时候,就会报错,因为构造函数的参数表示的不是类成员,而是赋给创元变量的值。如果这样做了的话,会造成混乱。所以参数名必须与成员变量名不相同。
构造函数的使用:
C++提供了两种构造函数 的使用方式:
1.显示调用:
CStack food = CStack(""Apple",1);
2.隐式调用:
CStack food("Apple",1);
即:这两种方式都是将food对象的m_name 赋值为Apple m_age 赋值为1.这两种方式 是等价的。
还有一种形式,我在这里提一下:
构造函数与new一起使用时:
CStack *food = new CSack("Apple",1);
这条语句将创建一个CStack对象,并将此对象的地址 赋值给food指针。那么我们就可以通过food指针来管理此对象。
默认构造函数:
默认构造函数用处:在未提供显式初始值时,用来创建对象的构造函数。
例如:
CStack food;
那么创建food对象时,将自行调用默认构造函数。
默认构造 函数的两种形式 :
1.无参数的默认构造函数:
例如:
CStack:: CStack()
{
m_name = '"Apple';
m_age = 1;
}
2.有初始值参数的默认构造函数 :
CStack::CStack(const string &name = "Apple",int age = 1)
{
m_name = name;
m_age = age;
}
要注意两点:
1.这两种方式是不能同时存在的,因为如果:
CStack food ;
那么声明food对象,自动调用默认构造函数时,会产生二义性(因为以上两种方式都是可以的),程序会报错 。
2.如果我们自己声明定义了一个构造函数不是默认的形式 ,而且没有声明定义默认构造函数(即使这个默认构造函数什么都没做),在以下情况下会出现错误:
CStack food;
因为在我们声明定义了一个非默认构造函数之后,编译器将不会再自动创建默认构造函数。这是因为C++规定:
当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。为类定义了构造函数之后,就必须为它提供默认构造函数。否则如果某些时候需要用到默认构造函数的情况下将会出错。而此时如果food要调用默认构造函数。这会造成冲突。
解决办法:
此时我们必须声明定义一个默认构造函数,来防止这样 的情况产生。
析构函数:
构造函数创建对象后,程序将跟踪对象 ,知道对象过期为止。对象过期后将调用一个 特殊的类成员函数来释放构造函数 用new申请的内存(如果没有申请,析构函数将不执行任何任务,,这种 情况下,只要让编译器生成一个默认的析构函数即可)。
析构函数特点:
1.形式:类名前 加上一个~符号。
2.没有参数。
3.没有返回值和声明类型。
4.用来释放在构造函数中用new申请的内存空间。
什么时候调用析构函数:
通常情况下析构函数不在代码 中 显示的的 调用。
1.创建 的是静态存储类对象时,在程序结束后自动调用。
2.创建 的是自动存储类对象 时,则程序在执行完 代码块后调用析构函数
3.如果是通过new来创建的对象,对象会位于 栈内存 或者*存储区。当使用delette释放 内存的时候 ,析构函数被调用 。
4.如果 是临时的对象,则在代码结束对临时对象使用的时候,自动调用析构函数。
即:类对象 在过期时自动 调用析构函数。因此必须有一个析构函数,如果用户没有定义析构函数,则编译器会隐式 的自动声明一个默认析构函数,
并在发现对象被删除后,自动的提供默认析构函数的定义。
例子:
(只需注意构造函数和析构函数即可)
#pragma once
#ifndef CSTOCK_H_
#define CSTOCK_H_
#include <string>
class CStock
{
public:
CStock(); //默认构造函数
CStock(const std::string &co, long n = 0, double pr = 0.0); //默认构造函数
void Buy(long num, double price);
void Sell(long num, double price);
void Update(double price);
void Show() const;
const CStock &TopVal(const CStock &s) const;
~CStock(); //析构函数
private:
std::string m_company;
int m_shares;
double m_shareVal;
double m_totalVal;
void SetTot(){ m_totalVal = m_shares *m_shareVal; }
};
#endif
#include "stdafx.h"
#include "Stock.h"
#include <iostream>
CStock::CStock() //默认的构造函数 将成员变量初始化
{
m_company = "no name";
m_shares = 0;
m_shareVal = 0;
m_totalVal = 0;
}
CStock::CStock(const std::string &co, long n, double pr) //如果需要外部参数,就传进来,这相当于函数重载
{
m_company = co;
if (n < 0)
{
std::cout << "Number of shares can't be negative;"
<< m_company << "shares set to 0.\n";
m_shares = 0;
}
else
{
m_shares = n;
}
m_shareVal = pr;
SetTot();
}
CStock::~CStock() //析构函数,由于我们并没有在构造函数中用new申请内存,所以析构函数无需执行任何操作、
{
}
void CStock::Buy(long num, double price)
{
if (num < 0)
{
std::cout << "Number of shares purrchsed can't be negative."
<< "Transaction is aborted\n";
}
else
{
m_shares += num;
m_shareVal = price;
SetTot();
}
}
void CStock::Sell(long num, double price)
{
using std::cout;
if (num < 0)
{
cout << "Number of shares sold can't be negative."
<< "Transaction is aborted.\n";
}
else if (num > m_shares)
{
cout << "You can't sell more than you have!"
<< "Transaction is aborted.\n";
}
else
{
m_shares -= num;
m_totalVal = price;
SetTot();
}
}
void CStock::Update(double price)
{
m_shareVal = price;
SetTot();
}
void CStock::Show() const
{
using std::cout;
using std::ios_base;
ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield);
std::streamsize prec = cout.precision(3);
cout << "Company :" << m_company
<< "shares:" << m_shares << "\n";
cout << "Share Price :" << m_shareVal;
cout.precision(2);
cout << "Total Worth: " << m_totalVal << std::endl;
cout.setf(orig, ios_base::floatfield);
cout.precision(prec);
}
const CStock &CStock::TopVal(const CStock &s) const
{
if (s.m_totalVal > m_totalVal)
return s;
else
return *this;
}
Main程序:
#include "stdafx.h"
#include <iostream>
#include "Stock.h"
const int STKS = 4;
int _tmain(int argc, _TCHAR* argv[])
{
CStock stocks[STKS] = { //通过构造函数在对象创建时对类对象的成员变量赋值(参数传进去)
CStock("NanoSmart", 12, 12.0),
CStock("Boffo objects", 200, 1.0),
CStock("Monolithic Obelisks", 130, 3.25),
CStock("Fleep EnterPrises", 60, 6.5)
};
std::cout << "Stock holdings:\n";
int st;
for (st = 0; st < STKS; st++)
{
stocks[st].Show();
}
const CStock* top = &stocks[0];
for (st = 1; st < STKS; st++)
{
top = &top->TopVal(stocks[st]);
}
std::cout << "\nMost valuble holding:\n";
top->Show();
return 0;
}
当然构造函数还有很多细节性的东西以及其他类型。想要深究的可以去学一下。
复制构造函数:
在程序 中,我们很多时候会让 一个对象等于另一个对象,这就要求自己定义函数,来将对象的每个值进行一次 赋值操作 ,在让 对象1 = 对象2 的操作时显式的调用复制函数。显然这是非常麻烦的,所以C++提供了复制构造函数,在让一个对象1 = 对象2时,自动调用复制构造函数,简化程序。
1.系统默认的复制构造函数:
如果用户没有自己写的复制构造函数,那么编译器会 自行创建一个默认的复制构造函数,它将 每一个成员变量都进行了复制。
2.自己声明定义复制构造函数:
例子:
如果在 让对象1 = 对象2时,你 想要 一部分成员变量不进行赋值,或者进行特殊的操作,则需要自己写想要 的复制构造函数。
由此引出深浅拷贝:
浅拷贝:
如果我们在程序将每个成员变量 依次拷贝复制时,将进行的是 浅拷贝。
深拷贝:
在程序中,如果兑现的构造函数中使用了new来为一些成员变量申请了内存,那么我们在执行复制构造函数 时,对象1 = 对象2 》对象1的这些成员变量 的内存相当于也是对象 2的成员变量的内存,这是一件很恐怖的事情,因为如果 我们在删除对象2的时候,相当于对象1的这些成员变量的内存被释放了,而当对象1 被删除时,释放对象1的这些成员变量的内存将是不存在,运行会报错。即:释放已经释放过的内存空间是一件很危险的时候。
那么这个时候我们需要自己写复制构造函数来实现深拷贝:
即:
另用new 申请内存空间来存放 从对象2中传过来的数据。
例子:
浅拷贝例子:
在上面代码的CStock类的.h文件中声明复制构造函数:
CStock(const CStock &s);
在.cpp 文件中给出实现:
CStock::CStock(const CStock &s)
{
m_shares = s.m_shares;
//.......
}
深拷贝例子:
如果在CStock类中存在一个 char *m_str成员变量,那么我们在复制时需要为其申请内存空间后才能执行赋值操作,如果只想赋值,会造成两个对象的m_Str只想同一块内存单元。
即:
CStock::CStock(const CStock &s)构造函数和析构函数并没有想象中的那么简单或者那么难,简单的是理解,难的是细节。
{
m_shares = s.m_shares;
//.......
m_Str = new char[1024]; //然后将s中的m_str指向的单元中的字符复制一份给本对象的m_Str内存中即可。
}
(介绍中很少提到new运算符和构造函数的联系运用等知识点,我打算重新写一份,将new单独出来。)
写完后会贴出链接。