Part 3
一 对象和类
1.类(Class)就像是一副蓝图,它决定一个对象将是什么样的(具备什么样的属性、功能)。
所以OOP过程的第一步是创建一个类,而每个类跟变量一样都有一个名字,我们就从如何声明一个类说起:
class MyFirstClass
{
} ;
就这样,我们创建了一个类!虽然它什么都干不了,但它是一个起点,一个成功的开始。
注意,类名的第一个字母采用大写是一种习惯的上的标准,但不是硬性规定。还有在类声明末尾,必须有一个分号,这一点跟C++结构情况相同。
类由变量和函数组成,对象将使用那些变量来存储信息,调用那些函数来完成操作。所以人们常常会看到一些专门术语:类里边的变量成为属性,函数成为方法。注意,他们的本质没有改变。
在类之中声明之后还要进行定义,而方法的定义通常安排在类声明的后面:
void Car::fillTank(float liter)
{
gas_tank += liter;
}
在这里有一个作用域解析操作符(::),作用是告诉编译器这个方法存在于何处,或者说是属于哪一个类
Eg
#include <iostream>
#include <windows.h>
#define FULL_GAS 85
class Car
{
public:
std::string color;
std::string engine;
unsigned int gas_tank;
unsigned int wheel;
void setColor(std::string col);
void setEngine(std::string eng);
void setWheel(unsigned int whe);
void fillTank(int liter);
int running(void);
void warning(void);
};
void Car::setColor(std::string col)
{
color = col;
}
void Car::setEngine(std::string eng)
{
engine = eng;
}
void Car::setWheel(unsigned int whe)
{
wheel = whe;
}
void Car::fillTank(int liter)
{
gas_tank += liter;
}
int Car::running(void)
{
std::cout << "我正在以120的时速往前移动。。。越过那高山越过那河。。。\n";
gas_tank--;
std::cout << "当前还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!\n";
return gas_tank;
}
void Car::warning(void)
{
std::cout << "WARNING!!" << "还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!";
}
// 这辆车还是存在一些BUG,你能够发现它并DEBUG它吗?
int main()
{
char i;
Car mycar;
mycar.setColor("WHITE");
mycar.setEngine("V8");
mycar.setWheel(4);
mycar.gas_tank = FULL_GAS;
while( mycar.running() )
{
if( mycar.running() < 10 )
{
mycar.warning();
std::cout << "请问是否需要加满油再行驶?(Y/N)\n";
std::cin >> i;
if( 'Y' == i || 'y' == i )
{
mycar.fillTank(FULL_GAS);
}
}
}
return 0;
}
改进的方法是每减少百分之一就问一次
注意
- C++允许在类里声明常量,但不允许对它进行赋值
class Car
{
public:
const float TANKSIZE = 85; // 出错@_@
}
绕开这一限制的方法就是创建一个静态常量
class Car
{
public:
static const float FULL_GAS = 85;
}
这种做法还有其他更高明的含义,以后我们将逐步介绍给大家
2.类似于使用结构的情况,可以在声明某个类的同时立刻创建一些该类的对象:
class Car
{
。。。。。。
} car1, car2;
这种做法在C++里是允许的。但作为一种良好的编程习惯,应该避免这种做法!
3.最后,假设我们有以下代码:
Car car1, car2;
car1.setColor(“WHITE”);
。。。。。。
car2 = car1;
把一个对象赋值给另一个同类的对象将会自动使同名的属性有同样的值。
一 构造器和析构器
面向对象的编程技术开发程序的步骤:
(1)定义一个有属性和方法的类(模板)
(2)为该类创建一个变量(实现)
1、构造器
构造器和通常的方法的主要区别:
(1)构造器的名字必须和它所在的类的名字一样;
(2)系统在创建某个类的实例时会第一时间自动调用这个类的构造器;
(3)构造器永远不会返回任何值
class Car
{
Car(void); //声明构造器
}
//定义构造器
Car::Car(void) //不用写void Car::Car(void)
{
color = "WHITE";
engine = "V8";
wheel = 4;
gas_tank = FULL_GAS;
}
#include <iostream>
#include <windows.h>
#define FULL_GAS 85
using namespace std;
class Car
{
public:
string color;
string engine;
float gas_tank;
unsigned int wheel;
//函数声明
Car(void); //构造函数
void setColor(string col);
void setEngine(string eng);
void setWheel(unsigned int whe);
void fill_tank(float liter);
int running(void);
void warning(void);
};
Car::Car(void) //定义构造函数
{
color = "WHITE";
engine = "V8";
wheel = 4;
gas_tank = FULL_GAS;
}
void Car::setColor(string col)
{
color = col;
}
void Car::setEngine(string eng)
{
engine = eng;
}
void Car::setWheel(unsigned int whe)
{
wheel = whe;
}
void Car::fill_tank(float liter)
{
gas_tank += liter;
}
int Car::running(void)
{
char i;
cout << "我正以120的时速往前移动..." << endl;
gas_tank--;
cout << "当前还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!" << endl;
if(gas_tank < 10)
{
warning();
cout << "请问是否需要加满油再行驶?[Y/N]" << endl;
cin >> i;
if('Y' == i || 'y' == i)
{
fill_tank(FULL_GAS);
}
}
if(0 == gas_tank)
{
cout << "抛锚中.....";
return 0;
}
return 1;
}
void Car::warning(void)
{
cout << "WARNING!!" << "还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!" << endl;
}
int main()
{
Car mycar;
while (mycar.running())
{
;
}
return 0;
}
每个类至少有一个构造器,如果没有在类里定义一个构造器,编译器就会使用如下语法替你定义一个:
CalssName::ClassName(){}
C++ 类构造函数初始化列表
分类 编程技术
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。例如:
class CExample {
public:
int a;
float b;
//构造函数初始化列表
CExample(): a(0),b(8.8)
{}
//构造函数内部赋值
CExample()
{
a=0;
b=8.8;
}
};
上面的例子中两个构造函数的结果是一样的。上面的构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。
初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。但有的时候必须用带有初始化列表的构造函数:
1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
2.const 成员或引用类型的成员。因为 const 对象或引用类型只能初始化,不能对他们赋值。
初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
首先把数据成员按类型分类并分情况说明:
1.内置数据类型,复合类型(指针,引用)- 在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
2.用户定义类型(类类型)- 结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)
注意点:
初始化列表的成员初始化顺序:
C++ 初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
class CMyClass {
CMyClass(int x, int y);
int m_x;
int m_y;
};
CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y)
{
};
你可能以为上面的代码将会首先做 m_y=I,然后做 m_x=m_y,最后它们有相同的值。但是编译器先初始化 m_x,然后是 m_y,,因为它们是按这样的顺序声明的。结果是 m_x 将有一个不可预测的值。有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。
原文地址:http://www.cnblogs.com/BlueTzar/articles/1223169.html
2、构造对象数组
Car mycar[10];
//调用语法
mycar[x].running;
3、定义析构器
一般来说构造器用来完成事先的初始化和准备工作(申请分配内存),析构器用来完成事后所必须的清理工作(清理内存)
析构器和构造器/类有着一样的名字,只是前边多了一个波浪符"~"前缀
class Car
{
Car(void);
~Car();
}
注意:
(1)析构器永远不返回任何值
(2)析构器不带参数,所以析构器的声明永远是如下格式: ~ClassName();
(3)在比较复杂的类里,析构器往往至关重要(可能引起内存泄露)
练习:
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
class StoreQuote
{
public:
string quote, speaker;
ofstream fileOutput;
StoreQuote();
~StoreQuote();
void inputQuote();
void inputSpeaker();
bool write();
};
StoreQuote::StoreQuote()
{
fileOutput.open("test.txt", ios::app); //打开文件,以追加的形式写入
}
StoreQuote::~StoreQuote()
{
fileOutput.close(); //关闭文件
}
void StoreQuote::inputQuote()
{
getline(cin, quote); //读取名言//读取一个字符串到数组quote中。
//getline()的原型是istream& getline ( istream &is , string &str , char delim );
其中 istream &is 表示一个输入流,譬如cin;string&str表示把从输入流读入的字符串存放在这个字符串中(可以自己随便命名,str什么的都可以);char delim表示遇到这个字符停止读入,在不设置的情况下系统默认该字符为'\n',也就是回车换行符(遇到回车停止读入)。
cout << "quote: " << quote << endl;
}
void StoreQuote::inputSpeaker()
{
cin.ignore(1);
getline(cin, speaker); //读取作者
}
bool StoreQuote::write()
{
if(fileOutput.is_open())
{
fileOutput << quote << "|" << speaker << endl; //写入文件
return true;
}
else
{
return false;
}
}
int main()
{
StoreQuote quote;
cout << "请输入一句名言: " << endl;
quote.inputQuote();
cout << "请输入作者: " << endl;
quote.inputSpeaker();
if(quote.write())
{
cout << "成功写入文件^_^" << endl;
}
else
{
cout << "写入文件失败T_T" << endl;
return 1;
}
return 0;
}
问题: 按照视频中的代码跑出现了一个bug,就是无法写入作者的名字,后来发现是把前一次的回车写进去了,也就是写入了空格.
解决方法: 在inputSpeaker()函数中加入cin.ignore(1), 表示忽略回车,写入正常