@cxm-2016
2016-09-30T10:51:19.000000Z
字数 4582
阅读 2021
c++
no
版本:2
作者:陈小默
说明:调整部分语序,修改部分别字
开发环境:Visual Studio 2010
当我们需要在对象创建时初始化一些数据的时候,我们不可能提供一个普通的成员方法供使用者在对象创建后调用。因为如果使用者故意或者无意间忘记了调用该方法,就可能导致程序出现偏离预期的结果。为了防止这种情况的发生,C++中提供了一种特殊的成员函数--构造函数。
构造函数与普通函数在声明与定义上别无二致,除了不需要声明返回值。
在声明中添加如下代码:
Student(std::string name,int chScore,int enScore,int mathScore);
一般实现方式为:
Student::Student(std::string name,int chScore=0,int enScore=0,int mathScore=0){
cout<<"您好呀! "<<name<<",很高兴见到你!"<<endl;
setName(name);
setChScore(chScore);
setEnScore(enScore);
setMathScore(mathScore);
}
当我们在类声明时声明了一个引用参数,而引用参数又要求必须初始化,且只能初始化一次。应该怎么办呢?这时我们需要一种新的成员初始化方式:
std::string & name;
比如我们的name属性是引用类型,那么我们需要在每一个构造方法实现
的参数列表后面加上这样的初始化器
Student::Student(std::string &n):name(n){
......
}
Student::Student(std::string &n,int chScore,int enScore,int mathScore):name(n){
......
}
如果我们有多个引用需要初始化,这些构造器只需要使用,
逗号分割,并且除了引用类型,任何类型属性都可以使用这种方式初始化
Class::Constructor(type *t1,type &t2,type t3...):t1(t1),t2(t2),t3(t3)...{}
当我们声明过一个构造函数之后,我们可以有三种方式去调用它:
1,显式的调用
Student jack = Student("jack",99,98,97);
2,隐式的调用
Student jack("jack",99,98,97);
3,使用new
运算符
Student *jack = new Student("jack",99,98,97);
上述3中方法的关键区别在于内存分配:前两种方式的对象将会被在栈中创建,第3中方式对象将会在堆中被创建
当我们没有显式的在声明中添加构造方法时,编译器会默认提供一个空参列表且没有任何行为的默认构造方法,就像下面这样
Student(){}
但是当我们声明了自己的构造方法之后,编译器便不会再提供默认的构造方法了。
当我们想定义一个默认的构造器有两种方式:
1,给已有的构造函数的所有参数提供默认值
Student(std::string name,int chScore=0,int enScore=0,int mathScore=0);
2,定义一个没有参数的函数重载
Student();
由于每种类型的构造器只能声明一个,所以同时使用上述两种方式会报错。
注意:声明隐式方式调用空参构造方法不能使用括号,因为这时编译器会将这行代码解释为函数声明,比如:
Student jack;//available
Student jack();//invalidate
析构函数是当对象被销毁时被系统调用的方法,一般用来清空内存。
析构函数的声明只用一种形式,就是~
加上类名
~Student();
析构函数的实现就可以根据需求去定义
Student::~Student(){
cout<<"再见! "<<name<<"."<<endl;
}
析构函数的执行与对象的创建位置相关
1,当对象被声明为全局变量,则当整个程序退出时,其析构函数才会被调用。
2,当对象被声明为自动存储类型(局部变量),当其生命周期结束,即其作用域执行完毕时,其析构函数会被调用。
3,当对象被声明为指针变量(使用new创建对象),当执行到delete方法时,其析构函数会被调用。
4,临时对象,这种对象在临时对象完成任务时自动调用其析构函数。实例演示中会专门介绍这种情况。
有如下代码:
const Student jack("Jack",99,98,97);
jack.show();//complier error
因为我们已经将jack对象声明为const
类型,所以当我们在调用show()方法时,编译器并不知道其中有没有更改对象内容的方法。所以拒绝编译。如果我们非要这么做的话,可以采用以下方式对show()函数重新定义
void show() const;
1,在头文件student.h
中声明类Student
//student.h -- Student class interface
//version 00
#ifndef STUDENT_H_
#define STUDENT_H_
#include <string>
class Student{ //class declaration
private:
std::string name;
int ch;
int en;
int math;
float average;
void count(){
average = (ch + en + math+0.0F)/3;
}
public:
Student();
Student(std::string name,int chScore,int enScore,int mathScore);
~Student();
void setName(std::string name);
void setChScore(int score);
void setEnScore(int score);
void setMathScore(int score);
void show();
};
#endif
2,在文件student.cpp
中实现类方法
//student.cpp -- implementing the Student class
//version 00
#include "stdafx.h"
#include "student.h"
Student::Student(){
cout<<"您好呀! 新同学,你叫什么名字?"<<endl;
}
Student::Student(std::string n,int chScore,int enScore,int mathScore):name(n),ch(chScore),en(enScore),math(mathScore){
cout<<"您好呀! "<<name<<",很高兴见到你!"<<endl;
}
Student::~Student(){
cout<<"再见! "<<name<<"."<<endl;
}
void Student::setChScore(int score){
Student::ch = score;
}
void Student::setName(std::string n){
Student::name = n;
}
void Student::setEnScore(int score){
en = score;
}
void Student::setMathScore(int score){
math = score;
}
void Student::show(){
Student::count();
ios_base::fmtflags orig = cout.setf(ios_base::fixed,ios_base::floatfield);
std::streamsize prec = cout.precision(1);
cout<<name<<" 同学的语文成绩为"<<ch<<"分,数学成绩为"<<math<<"分,英语成绩为"<<en<<"分,平均成绩"<<average<<"分"<<endl;
cout.setf(orig,ios_base::floatfield);
}
3,在mian方法中调用
//visual studio 2010 --main program
#include "stdafx.h"
#include "student.h"
int _tmain(int argc, _TCHAR* argv[]){
Student *s1,s2,s3("Jack",90,80,70);
s1 = new Student("Sam",90,95,100);
s1->show();
s2 = Student("Sue",85,90,95);
s2.show();
s3.show();
s1 = new Student("Joe",91,92,93);
s1->show();
delete s1;
return 0;
}
根据上面的学习内容你猜猜运行结果是什么?
运行结果:
您好呀! 新同学,你叫什么名字?
您好呀! Jack,很高兴见到你!
您好呀! Sam,很高兴见到你!
Sam 同学的语文成绩为90分,数学成绩为100分,英语成绩为95分,平均成绩95.0分
您好呀! Sue,很高兴见到你!
再见! Sue.
Sue 同学的语文成绩为85分,数学成绩为95分,英语成绩为90分,平均成绩90.0分
Jack 同学的语文成绩为90分,数学成绩为70分,英语成绩为80分,平均成绩80.0分
您好呀! Joe,很高兴见到你!
Joe 同学的语文成绩为91分,数学成绩为93分,英语成绩为92分,平均成绩92.0分
再见! Joe.
再见! Jack.
再见! Sue.
请按任意键继续. . .
我们来一步一步的分析代码执行流程,
Student *s1,s2,s3("Jack",90,80,70);
这一句代码声明了三个变量,并且定义了其中的两个!!! 这里声明了一个Student的指针类型s1,声明并定义了Student类型的变量s2,s3,后面的两种定义方式都是隐式的,只不过s2的定义如上述 默认构造函数
所述,容易使人产生这里仅仅是声明而不是定义的错觉。
s2 = Student("Sue",85,90,95);
现在再来看这一句,这里产生了一个上述临时对象
。这个临时对象会将其内部的值复制到s2的空间中,其后自动销毁。那么为什么不让s2的引用指向这个新建立的对象呢?这就要从变量内存上分析,由于我们的s2和s3是在栈中分配的内存,而栈又会随着方法的退出而销毁,就导致了栈中的对象是不稳定的。如果我们采用指向的方式,那么我们就不知道什么时间我们指向的栈帧就会撤销,这时我们再去访问我们的方法就会报错了。
s1 = new Student("Joe",91,92,93);
由于s1是在堆中分配的内存,所以我们才能在new时直接传递引用,但是这种写法是一个不好的编程习惯。因为我们在给指针重新分配内存的时候并没有释放原空间,如果产生大量这种代码将会造成内存溢出
最后,为什么先释放的是s3,后释放的是32呢?是由于栈的FILO性质导致的。