对象和类
指定基本类型意味着:
- 数据对象需要的内存数量;
- 如何解释内存中的位(long和float在内存中占用的位数相同,但将他们转换为数值的方法不同)
- 数据对象可执行的操作或方法。
c++中的类
将抽象转换为用户定义类型的c++工具,将数据表示和操纵数据的方法组合成一个整洁的包。
接口:接口让程序员能够编写与类对象交互的代码,从而让程序能够使用类对象。例如,计算string对象中包含多少个字符,无需打开对象,只需使用string类提供的size()方法。
//stock00.h --stock class interface
#ifndef STOCK00_H_
#define STOCK00_H_
#include <string>
class Stock{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot(){total_val=shares*share_val;}
public:
void acquire(const std::string & co,long n,double pr);
void buy(long sum,double price);
void sell(long sum,double price);
void update(double price);
void show();
};
#endif
-
访问控制
类对象的程序可以直接访问公有部分public,但只能通过公有成员来访问对象的私有成员private。公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。
防止程序直接访问数据被称为数据隐藏。
#include <iostream> #include "stock00.h" void Stock::acquire(const std::string&co,long n,double pr) { company=co; if(n<0){ std::cout<<"numbers of shares can't be negative;"<<company<<"\n"; shares=0; } else shares=n; share_val=pr; set_tot(); } void Stock::buy(long num,double price){ if(num<0){ std::cout<<"numbers of shares purchased can't be negative." <<"transction is aborted.\n"; } else{ shares+=num; share_val=price; set_tot(); } } void Stock::sell(long num,double price){ if(num<0){ std::cout<<"numbers of shares purchased can't be negative." <<"transction is aborted.\n"; } else if (num>shares){ std::cout<<"you can't sell more than you have!" <<"transction is aborted.\n"; } else{ shares-=num; share_val=price; set_tot(); } } void Stock::update(double price){ share_val=price; set_tot(); } void Stock::show(){ std::cout<<"company:"<<company <<"shares: "<<shares<<"\n" <<" share price: $"<< share_val <<"total worth :$"<< total_val <<'\n'; } int main(){ Stock fluffy_the_cat; fluffy_the_cat.acquire("Nano",20,12.50); fluffy_the_cat.show(); fluffy_the_cat.buy(15,18.125); fluffy_the_cat.show(); }
-
成员函数说明
acquire()函数管理对某个公司股票的首次购买,而buy()和sell()管理增加或减少持有的股票。方法buy()和sell()确保买入或卖出的股数不为负、如果用户试图卖出超过他持有的股票数量,则sell()函数结束本次交易。
这种使数据私有并限于对公有函数访问的技术允许我们能够控制数据如何被使用。
4个成员函数重新设置了total_val的值,这个类并非将计算代码编写4次,而是让每个函数都调用set_tot()函数——编写这个类的人可以使用,而编写代码使用这个类的人不能使用。
-
内联方法
也可以在类声明之外定义成员函数,使用inline即可:
class Stock{ private: ... void set_tot(); public: ... }; inline void Stock::set_tot() { total_val=shares*share_val; } #include <iostream> int main(){ Stock fluffy_the_cat; fluffy_the_cat.acquire("Nano",20,12.50); fluffy_the_cat.show(); fluffy_the_cat.buy(15,18.125); fluffy_the_cat.show(); }
类的结构函数和析构函数
声明和定义构造函数
Stock::Stock(const string & co,long n,double pr){
company=co;
if(n<0){
std::cerr<<"Number of shares can't be negative; "
<<company<<"shares set to 0.\n";
shares =0;
}
else
shares=n;
share_val=pr;
set_tot();
}
上述代码和本章前面的函数acquire()相同,区别在于,程序声明对象时,将自动调用构造函数。
注意不要讲类成员名称用作构造函数的参数名。构造函数的参数表示的不是类成员,而是赋给类成员的值。为避免这种状况,一种做法是在数据成员中使用m_前缀,或在数据成员后加 _。
- 使用构造函数
Stock garment(“Furry”,50,2.5); //较为紧凑
Stock garment=Stock(“Furry”,50,2.5);
Stock *pstock=new Stock(“Games”,18,19.0);//创建一个Stock对象,并将该对象的地址赋给pstock指针
默认构造函数
Stock fluffy;//默认函数如下:Stock::Stock(){ },因此该语句创建fluffy对象,但不初始化其成员
定义默认构造函数的方式有两种,一种是给已有构造函数的所有参数提供默认值
为类提供了构造函数后,就必须为它提供默认构造函数。
- 定义默认构造函数:
Stock(const string & co="Error",int n=0,double pr=0.0);
另一种是通过函数重载来定义另一个构造函数——一个没有参数的构造函数:
Stock();
创建了默认构造函数后,便可以声明对象变量,而不对它们进行显式初始化:
Stock first; //隐式地调用默认构造函数
Stock first=Stock(); //显式地调用构造函数
Stock first(”con“); //调用非默认构造函数,接受参数的构造函数
Stock second(); //second()是一个返回Stock对象的函数
析构函数
析构函数负责清理过期的对象,如果构造函数使用new来分配内存,析构函数将使用delete来释放这些内存。由于他不承担任何重要的工作,因此可以将它编写为不执行任何操作的函数:
Stock::~Stock()
{
}
#ifndef STOCK00_H_
#define STOCK00_H_
#include <string>
class Stock{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot(){total_val=shares*share_val;}
public:
//two constructors加入两种结构函数的定义
Stock(); //default constructor默认结构函数的定义
Stock(const std::string & co,long n=0,double pr=0.0);
~Stock(); //析构函数的定义
void buy(long sum,double price);
void sell(long sum,double price);
void update(double price);
void show();
};
#endif
在cpp文件中加入结构函数和析构函数的方法:
#include <iostream>
#include "stock10.h"
Stock::Stock(){
std::cout<<"default constructor called\n";
company="no name";
shares=0;
share_val=0.0;
total_val=0.0;
}
Stock::Stock(const std::string &co,long n,double pr){
std::cout<<"Constructor using "<<co<<" called\n";
company=co;
if (n<0){
std::cout<<"Number of shares can't be negative;"
<<company<<"shares set to 0.\n";
shares=0;
}
else
shares=n;
share_val=pr;
set_tot();
}
Stock::~Stock(){
std::cout<<"Bye, "<<company<<"!\n";
}
void Stock::buy(long num,double price){
if(num<0){
std::cout<<"numbers of shares purchased can't be negative."
<<"transction is aborted.\n";
}
else{
shares+=num;
share_val=price;
set_tot();
}
}
void Stock::sell(long num,double price){
if(num<0){
std::cout<<"numbers of shares purchased can't be negative."
<<"transction is aborted.\n";
}
else if (num>shares){
std::cout<<"you can't sell more than you have!"
<<"transction is aborted.\n";
}
else{
shares-=num;
share_val=price;
set_tot();
}
}
void Stock::update(double price){
share_val=price;
set_tot();
}
void Stock::show(){
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);
std::cout<<"company:"<<company <<"shares: "<<shares<<"\n"
<<" share price: $"<< share_val <<"total worth :$"<< total_val <<'\n';
cout.setf(orig,ios_base::floatfield);
cout.precision(prec);
}
int main(){
{
using std::cout;
cout<<"Using constructors to create new objects\n";
Stock stock1("Nan",12,20.0);
stock1.show();
Stock stock2=Stock("Bof",2,2.0);
stock2.show();
cout<<"Assigning stock1 to stock2:\n";
stock2=stock1;
cout<<"Listing stock1 and stock2:\n";
stock1.show();
stock2.show();
}
return 0;
}
将列表初始化用于类:
Stock hot_tip={”der“,100,45.0};
Stock jock{”sport“};
将使用构造函数来创建这两个对象,创建对象jock时,第二和第三个参数将为默认值0和0.0。
const成员函数
void Stock::show() const //保证类方法不修改调用对象,就将其声明为const
this指针
涉及到两个对象的情况下,需要使用c++的this指针。比如查看两个Stock对象,返回total_val较高那个对象的引用。命名该方法为topval(),函数调用Stock1.topval()访问Stock1的对象,调用Stock2.topval()访问Stock2对象的数据,如果希望两个对象进行比较,则必须将第二个对象作为参数传递给它。
在头文件中添加:
const Stock & topval(const Stock & s) const;
在主函数中添加:
const Stock & Stock::topval(const Stock & s) const
{
if(s.total_val>total_val)
return s;
else
return *this;
}
this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法)
对象数组
声明对象数组和标准类型数组相同:
Stock mystuff[4];
mystuff[0].update();
const Stock * tops=mystuff[2].topval(mystuff[1]);//比较第二和第三个元素,并设置为tops
用构造函数初始化数组元素,必须为每个元素调用构造函数。
如果类包含多个构造函数,则可以对不同的元素使用不同的构造函数,未被初始化的元素将使用默认构造函数进行初始化。
类作用域
-
作用域为类的常量
类声明使用30来指定数组的长度,由于该常量对所有对象来说都是一样的,因此创建一个由所有对象共享的常量。
class Bakery{ private: const int Mouths =12;//这种声明方式是行不通的,声明类只是描述了对象的形式,并没有创建对象 double costs[Mouths]; } //可以使用的方式 class Bakery{ private: enum {Mouths =12};//在类中声明一个枚举,用这种方式并不会创建类数据成员,即所有对象都不包含枚举 double costs[Mouths]; } class Bakery{ private: static const int Mouths =12;//创建一个名为Mouths的常量,该常量将与其他静态变量存储在一起,而不是存储在对象中 double costs[Mouths]; }
-
作用域内枚举
传统的枚举存在一些问题,比如两个枚举定义中的枚举量可能发生冲突:
enum egg{Small,Medium,Large,Jumbo}; enum t_shirt{Small,Medium,Large,Xlarge};//二者位于相同的作用域内,将发生冲突 enum class egg{Small,Medium,Large,Jumbo}; enum class t_shirt{Small,Medium,Large,Xlarge};//枚举的作用域为类,这样就不会冲突了