黑马程序员---第十章 对象和类

来源:互联网 发布:陕西广电网络上班时间 编辑:程序博客网 时间:2024/05/29 21:28

-----------android培训java培训、java学习型技术博客、期待与您交流!------------ 

一,基本概念。

1.OOP(object oriented programming )特性:抽象,封装和数据隐藏,多态,继存,代码的可重用性 。
2.接口:一个共享窗口,共两个系统(如 计算机与打印机,用户和计算机)交互时使用。

3.类名:为识别类名,一般约定将类名首字母大写

4.类成员访问控制:private 和 public 。使用类对象的程序都可以直接访问公有部分,但只能通过公有函数成员函数来访问私有成员。

公有函数成员是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。防止程序直接访问数据被称为数据隐藏。

5.类与结构体:类中默认对象为 private  结构体默认对象为 public 。c++通常使用类来实现描述,而把结构体限制为只表示纯粹的数据对象或没有私有部分的类。

6.实现类成员函数

  ① 成员函数的函数头使用作用域解析操作符(::)类指出函数所属的类。如:void stock ::update( double price ) ;

  ② 方法可以访问类的私有成员,非成员函数访问私有成员数据编译器将禁止这种操作(但是友元函数除外) 如:cout<<"company"<<company<<endl ;


二、项目实例:


//头文件
#ifndef STOCK1_H_
#define STOCK1_H_
//#include <string>

class Stock
{
public://(公有成员函数)
Stock();//显示构造函数(类变量初始化)
~Stock();//显示析构函数(释放类的空间)
Stock(const char*co , int n = 0 ,double pr = 0.0 );//(类成员变量赋值)
void buy(int num ,double price);//新增加的股票数
void sell(int num ,double price);//卖掉的股票数
void update(double price);//价格更新
void show();//显示当前的股权持有状况
private://(私有成员函数)
char compay[30] ;//公司名称
//string compay ;
int shares ;//持股数量
double share_val ;//每股单价
double total_val ;//股票总额
void set_tot(){total_val = share_val * shares ;}//内联函数计算股票总价
};

#endif


//实现文件
#include <iostream>
#include <string>
#include "stock1.h"

Stock::Stock() 
{
std::cout<<"default constructor called .\n";
std::strcpy(compay,"no name");
shares = 0 ;
share_val = 0.0 ;
total_val = 0.0 ;
}

Stock::~Stock()
{
std::cout<<"bye ,"<<compay<<"! \n";
}

Stock::Stock( const char*co , int n /*= 0 */,double pr /*= 0.0 */ )
{
using std ::endl ;
std::strncpy(compay,co,29);
compay[29] = '\0' ;
//string compay = co ;  //改写
if ( n<0)
{
std::cerr<<"number of shares can't be negative ."<<compay<<"shares set to 0 ."<<endl;
}
else
{
shares = n ;
share_val = pr ;
set_tot() ;
}
}

void Stock::buy(int num ,double price)
{
if (num<0)
{
std::cerr<<"number of shares purchased can't be negative ."
<<"transatction is aborted .\n";
}
else
{
shares += num ;
share_val = price ;
set_tot() ;
}
}


void Stock::sell(int num,double price)
{
using std::cerr ;
if (num<0)
{
cerr<<"number of shares sold can't be negative ."
<<"transation is abored . \n ";
}
else if (num>0)
{
cerr <<"you can not sell more than you have ."
<<"transation is abored . \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 :: endl ;
//cout<<"fadfadfasf"<<endl;
cout<<"compay : "<<compay <<endl ;
cout<<"shares : "<<shares <<endl ;
cout<<"share price $ : "<<share_val <<endl ;
cout<<"total worth $ : "<<total_val <<endl ;
}


//客户文件
#include <iostream>
//#define  STOCK1_H_
#include "stock1.h"

int main()
{
using namespace std ;
using std ::ios_base ;
cout.precision(2);
cout.setf(ios_base::fixed,ios_base::floatfield);
cout.setf(ios_base::showpoint);


//Stock *xxx = new Stock;
//delete xxx;


cout<<"using constructors to create new objects \n" ;
Stock stock1 ;
stock1.show() ;
Stock stock2 = Stock("boffo objects ",2,2.0);
stock2.show() ;


cout<<"assinging 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 = Stock("nifty foods",10,50.0);
cout<<"revised stock1 : \n";
stock1.show() ;
cout << "done \n";

return 0 ;
}



三、

1.strncpy(s1,s2,n) 

    当n>s2 时,把s2 中字符串复制到s1中,后面通过‘\0’补足,当n<s2 时 s2中前n个字符复制过去,但由于后面没有空值字符,因此不是字符串,因此后需要添加'\0'

2.和cout一样,cerr 也是一个ostream 对象,他们之间的区别在于,操作系统重定向只影响cout,而不影响cerr ??????????

3.内联函数的特殊性要求在每个使用他们的文件中都对其进行定义,确保内联定义对多文件程序中的所有文件都可用,最简单的方法是:将内联定义放在定义类的头文件中

4.调用成员函数时,它将使用被用来调用他的对象的数据成员。所创建的每个新对象都有自己的存储空间,用于存储其内部变量和类成员,但同一个类的所有对象共享同一组

   内方法,即每种方法只有一个副本。

5.客户/服务器模型:客户只能通过以公有方式定义的接口使用服务器,这意味着客户(客户程序员)唯一的责任是了解接口,服务器(服务器设计人员)的责任是确保服务器根    据该接口可靠并准确地执行。

6.小结。

  ①类设计的第一步:提供类声明。

     类声明类似结构声明,可以包括数据成员和函数成员。声明有私有,其中声明的成员只能通过成员函数进行访问。声明还有公共部分,其中声明的成员可被使用类对象的程        序直接访问。通常情况下,数据成员被放在私有部分,成员函数被放在公有部分中。

     如:

calss classname
{
private:
data member declarations ;
public:
member function prototypes ;
};

  ②第二步:实现类成员函数。

      在类声明中提供完整的函数定义,而不是函数原型,但是通常的做法是单独提供函数定义(除非函数很小,内联),这种情况下需要使用作用域解析操作符来指出成员函数

      属于哪个类。如:char *Bozo :: Retort() 



 四、类的构造函数和析构函数

(一)构造函数

1.构造函数的原因:因为类中的数据有部分是私用的,这意味程序不能直接访问数据,程序只能通过成员函数来访问数据成员,因此需要设计合适的成员函数才能成功地将对象     初始化。(构造函数没有被声明类型

   如:Stock::Stock( const char*co , int n = 0 ,double pr = 0.0  )  

    此代码与acpuire() 函数相似,但是区别在于,程序声明对象时,将自动调用构造函数。类成员变量一般用 m_name  加以区别

2.使用构造函数:(c++提供了两种方式来初始化对象的方法)

  ① 显式的调用构造函数

       Stock food = Stock( "word cabbage" , 250 , 1.25 ) ;

  ②隐式地调用构造函数:

     Stock garment = ("furry mason" , 50 ,2.5 );

3.构造函数与new 的结合

  Stock  *pstock = new Stock ("electroshock game" , 18 , 19.0 ) ;

4.默认构造函数:在没有提供显式的初始化值时,被用来创建对象的构造函数。如:Stock stock1 ;(仅有用默认构造函数)可能调用:Stock::Stock () { } 

   重点:当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。为类定义了构造函数后,程序员必须为他提供默认构造函数。如果提供了非默认构造                   函数  但是没有提供默认构造函数,程序将报错。Stock stock1 ;(提供构造函数但是没有提供默认构造函数) 原因:禁止创建为初始化的对象。

5.默认构造函数的两种方式:

   第一种:给已有构造函数的所有参数提供默认值:如:Stock::Stock( const char*co = "error" , int n = 0 ,double pr = 0.0  ) ;

   第二种:函数重载来定义另一个构造函数(没有参数的构造函数):Stock() ;

(二)析构函数:当类对象过期时,程序将自动调用一个特殊的成员函数,完成清理工作。

1.如果构造函数使用new 来分配内存,则析构函数将使用delete 类释放内存,没有使用new的类对象,将生成一个隐式析构函数即可。

2.对象过期时自动调用析构函数,如果程序员没有提供析构函数,编译器将隐式声明一个默认的析构函数,并在对象呗删除代码后,提供西宫函数的定义。

3.什么时候调用析构函数有编译器决定,通常不应该显式调用析构函数,一般情况如下:

   静态存储类对象,程序结束时自动调用;自动存储类对象,程序执行完代码块时自动调用;new创建,驻留在堆栈中或自由存储区,使用delete来释放内存;

   建立的临时对象完成的特定的操作,程序将在结束对该对象的使用时调用。

4.析构函数的形式:

   Stock ::Stock () { } ;

(三) const成员函数用法

const 声明 :typedef fun_name( parameter ) const ;       for example : void show () const ;

const 定义 :typedef class_name :: fun_name ( parameter ) const ;      for  example : void Stock :: show () const ;

说明 : 只要类方法不修改调用对象,就应该将其声明为const 

(四)小结

           1.构造函数的名称和类名称相同,但通过函数重载,可以创建多个同名的构造函数,条件是每个构造函数的特征标都不相同。另外,构造函数没有声明类型。

通常,构造函数用于初始化类对象的成员,初始化应与构造函数的参数列表匹配。

   2.如果构造函数只有一个参数,还可以这样初始化对象。

如:Bozo ( int age ) ;

Bozo tubby = 32 ; // special form for one-argument constructors 


四、this 指针 :this 指针指向用来调用成员函数的对象(this 被作为隐藏参数传递给方法 )this 只能用于非静态成员函数内部

       1.说明:① 每个成员函数(包括构造函数和析构函数)都有一个this指针,this指针指向调用对象,如果方法需要引用整个调用对象,则可以使用表达式*this ,

在函数后面使用const 限定符将 this 限定为 const 这样将不能使用 this 来修改对象的值 。

      ② 不过 要返回的不是this ,this是对象的地址,而是对象本身,即*this (将解除引用操作符*用于指针,将得到指针指向的值 )

2.例题:

声明:const Stock & topval (const Stock & s)  const

定义:

const Stock & Stock::topval (const Stock & s)  const
{
if (s.total_val > share_val )
{
return s ;
}
else
{
return *this ;
}
}


五、对象数组

1.初始化方式:const int STKS = 8 ;


Stock stocks[ STKS] ={
Stock("nanosmart",12,20.0),  
// Stock( const char*co , int n   ,double pr   );来初始化stock[0],stock[1],stock[3],stock[4]
Stock("bofffo objects",200,2.0),
Stock();
//用stock()来初始化stock[2]
Stock("monolithic obelisks",130,3.25),
Stock("fleep enterprises",60,6.5)
//余下的五个类对象使用默认构造函数进行初始化
}

2.接口和实现小结:修改类的私有部分和实现文件属于实现变更;修改类的公有部分属于接口变更。实现变更改变了类的工作原理,接口变更改变了使用类的人可用的编码方         式。

3.类作用域

  说明:在类中定义的名称(如类数据成员和类成员函数名)的作用域都为整个类,作用域为整个类的名称只在该类中是已知的,在类外事不可知的。因此,可以再不同类中

使用相同的类成员名而不会引起冲突。另外类作用域意味着不能从外部直接访问类的成员,公有成员函数也是如此。也就是说,要调用公有成员函数,必须通过对象。

4.作用域为整个类的常量

   ① 说明:

class stock
{
private:
const int len = 30 ; // decalare a constant ? fails !!!
char company[len] ;
};
//类声明只是描述了对象的形式,并没有真正创建对象,因此,再被对象创建之前,将没有
//用于存储对象值的空间

   ②创建类常量的方法:

第一种方法:声明一个枚举。

class stock
{
private:
enum { len = 30 };  //class-specific constant 
char company[len] ;
};
//说明:1.不会创建数据成员,也就是说,所有对象中都不包含枚举
//  2.len只是一个符号名称,编译器遇到后 30 来代替


第二种方法:使用static关键字

class stock
{
private:
static const int len = 30;  //declare a constant ! work !
char company[len] ;
};
//说明:len是常量,与静态变量储存在一起,而不是在对象中,因此只有一个len ,被stock共享





六、总结

1.一般来说,私有成员存储信息,公有成员函数(又成方法)提供访问数据的唯一途径。类将数据和方法组合成一个单元,其私有性实现数据隐藏。

2.分类管理:类声明(包括由数据原型表示的方法)应放在头文件中,定义成员数的源代码放在方法文件中,这样便将接口描述与实现细节分开,从理论上,只需要知道公共接口就可以实现使用类。对象时类的实例,这意味着对象是这种类型的变量。














0 0
原创粉丝点击