c++对象学习笔记知识介绍

c++对象学习笔记知识介绍

自己的学习笔记,C++中关于对象和类的说明

I

***类,多么富有艺术性的词!想一想能把真实世界中的一切在它中模拟实现,就让人兴奋不已。掌握了它也就掌握了程序艺术的真谛、也就掌握了打通了现实与虚幻的时空门!---Skyala***

Skyala请您记住下面惊人的相似规律:
人(类)->父母(对象)->子女(继承、多重继承)->子孙(多态性)---哇,My God!多么完美的曲线!

面向对象程序设计总结:向对象发送消息。
可以减少函数传递过程中的参数
建立的类要有用,实用为准!


一、类杂谈
1。定义:
类:对实体的属性和行为的一般描述。Skyala注:太完美的概念是定义不来的
是程序设计中的基本单位。可以说是一种新的数据类型,所以有这么多的面向对象程序设计语言。太灵活了

类的数据部分:数据成员(data member) ,其初始化不能在定义体中声明它们的地方初始化,而应该用类的构造函数初始化或给它们设置值的函数赋值。
最好保持类的所有数据成员都是私有的,提供函数来操作这些数据,可隐藏类的实现、减少错误,提高可修改性。
外部不能访问类的私有成员,只能利用成员函数。记住main也是一个类的外部

类的函数部分:成员函数(member function),在外部访问类公有成员函数,也要用来成员访问运算符

对象:由类产生的大量的项称为对象也即“存储的一个区域”
对象是类的最终实例化。
对象初始化 Time T(23)在对象名右,分号前的圆括号中

类是C中结构的自然延伸。

2。类的成员访问运算符:
圆点:通过对象名或对对象的引用访问 p.t
箭头:通过指向对象的指针访问 p->t

4。构造函数与析构函数
a.与类同名的成员函数叫“构造函数(constructor)”,用来初始化类的对象的数据成员。
构造函数:没有返回类型--所以没有返回值,可重载
给构造函数提供默认参数值即使调用构造函数时没有提供参数值,也会确保按默认参数初始化。所有的参数都是默认参数的也是默认构造函数。
不要在构造函数中调用其它函数(虽然这是允许的)但在初始化正确地完成之前使用数据成员可能会导致错误

b. 与类同名但加了“~”(按位取反运算符)的成员函数叫“析构函数(destructor)”,系统回收内存前 做清理工作
析构函数:没有返回类型--所以没有返回值,不能重载--所以在类中只有一个

c.在对象的建立和撤销时分别自动调用相应的构造和析构函数,两者的调用顺序正好相反
根据对象的不同:全局作用域(程序终止)、局部对象(声明对象的程序块)、静态static局部对象(程序终止,但在全局前)

5。 接口和实现
类的定义:包括数据成员和成员函数的声明,引处的成员函数声明也即函数的原型。
要用分号结束类的定义
接口:类的公有函数(访问说明符public中的函数)提供的操作该类的数据成员的方法,此种函数也叫接 口
把类的声明放入某个头文件中构成类的公有接口
实现:类的成员函数的定义部分,在外部定义的要用双目运算符“::”使用成员函数,
把成员函数的定义放入某个源文件中,从而构成类的实现--信息隐藏
用户可以访问类的接口,便不能访问类的实现
好处:只要接口没变,实现改变了只要使用类的代码重新编译而不需改动。

**注软件工程的基本原则:
最低访问权原则:(当然相对于用户了--类的使用者)
除了很小的函数外,所有的成员函数都应在类定义体外定义,有利于接口和实现分离。能鼓励独立软件销售商(ISV)类库作为商品 ---Skyala:全部行业有公用类,统一规化软件类库,多么有趣的一件事

6。一个成员函数中类的内部定义的会自动成为内联函数(inline),外部的要加inline。
7。防止多次包含相同的头文件time.h为头文件名,TIME为假设的类,"_"代替".",此处运用了预处理指令
#ifndef TIME_H
#define TIME_H
.
.
#endif
8。公有(Public)、私有(private)、受保护(protected)
私有数据成员只能被本类的成员函数或类的友元访问
类的默认情况下是私有访问类型
9。访问函数及工具函数
set 设置函数,可进行合法性检查。get 获取函数的地位。
不仅有助于保护数据的完整性,而且也使得数据成员的实现方式对客户隐藏起来

10。不要让公有成员函数返回对私有数据成员的非常量引用(或指针)
如下:
public:
int &badset();
int Time::badset(inthh)
{
return hour;
}
T.badset(12) = 74; //返回的引用函数调用可作左值。

11。“=”可以将一个类赋值给同类型的另一个类,通过逐个成员拷贝的默认赋值方式实现的


应用举例:LeapYear.h、LeapYear.cpp
注:这是个闰年的计算,不过本人不满意,有点儿乱。
编译环境:Window2000 Vc6.0
#ifndef LEAPYEAR_H //预处理指令,防止包含多个同名头文件
#define LEAPYEAR_H //"-"代替"."
class LeapYear
{
public:
//可以给构造函数提供默认参数,无返回值无返回类型
LeapYear (int, int, int);
~LeapYear ();
int getYear ();
void setMonth (int);
int getDay ();
void print () const; //const成员函数

private:
int iYear;
int iMonth;
//int iday; 要注意大小不要输入错误
int iDay;
int checkDay (int); //工具函数
}; //类定义结束
#endif


#include <iostream>
#include <string>
#include "LeapYear.h"
using namespace std;

//成员初始化
LeapYear::LeapYear (int iY, int iM ,int iD)
{
iMonth = iM;
iYear = iY;
//iDay = checkDay (int iD); //函数调用格式错误
iDay = checkDay (iD);
cout << "constructor sucessed!" << endl;
}

LeapYear::~LeapYear()
{
cout << "对象内存释放成功!" << endl;
}

int LeapYear::getYear ()
{
return iYear;
}

void LeapYear::setMonth (int iM)
{
iMonth = (iM < 12 && iM > 1)?iM: 1;
}

int LeapYear::getDay ()
{
return iDay;
}

int LeapYear::checkDay (int testDay)
{
static int dayPerMonth [13] ={0, 31, 28, 31,30,
31, 30, 31, 31,
30, 31, 30, 31};

if (iMonth != 2)
{
if (testDay > 0 && testDay <= dayPerMonth [iMonth])
return dayPerMonth [iMonth];
}
else
{
//闰年计算公式
int days = (iYear % 400 == 0 ||
(iYear % 4 == 0 && iYear % 100 != 0)?29: 28);
if (testDay > 0 && testDay <= days)
return days;
}

cout << "今天Day" << testDay << "invalid set to day /n";
return 1;
}

void LeapYear::print () const
{
cout << iYear << '/' << iMonth << '/' << iDay;
}

void main()
{
LeapYear lyDay (1994, 2, 4);
// lyDay.setDay (4);
lyDay.print ();
cout << "年份" << lyDay.getYear () << endl;
cout << "月日" << lyDay.getDay () << endl;
}

 

II

***再续前情,此生不待 ---Skyala***

Skyala请您记住下面惊人的相似规律:
人(类)->父母(对象)->子女(继承、多重继承)->子孙(多态性)---哇,My God!多么完美的曲线!

面向对象程序设计总结:向对象发送消息。
可以减少函数传递过程中的参数
建立的类要有用,实用为准!


从此部分开始笔记中加入声明功能,并在每一定义部分后加入小程序(伪代码)用来讲解,方便多了。

*************************************声明*****************************
1. const对象和const函数
2. 友元(friend)类和友元函数
3. 静态(static)数据成员、静态数据函数及静态对象
4. 模块(template)类
5. 其他杂述(复合、包容器类、递取类......)
* 信息隐藏:对客户隐藏实现细节
类定义了抽象数据类型(ADT):表示了数据及对数据的操作
C++强调了数据的重要性
* char * temp = new char [stlen (fristname) + 1];
assert (temp != 0); //确认内存分配是否成功

6. 应用举例
a. 堆栈模块类 (TStack.cpp)
b. 遍历包容器类的递取类(Queue类)
c. List类
7.问题:
this指针不理解
为什么不能用const声明静态成员函数


************************************定义******************************
1. const对象和const函数
* const对象不能调用非const函数、不能修改const对象
const对象和const变量是不能赋值的,必须要初始化。在构造函数中可以用成员初始值进行初始化
* const成员函数不能调用非const成员函数、不能修改对象的数据成员
非const成员函数可重载为const成员函数
* 特例:当声明一个const对象时与构造函数和析构函数肯定会修改对象矛盾,此处这两个函数都不要声明关键字const,为了能正确初始化对象,允许这两个函数修改对象
* 切记:当一个类包括const对象时,必须给构造函数提供成员初始化值
class Time
{
Time(int = 0, int = 0);
void setTime(int = 0,int = 0,int = 0); //默认构造函数
int getHour() const {return hour;} //const成员函数

private:
int hour;
int time const; //const数据成员,要进行成员赋值初始化
}

Time::Time(int s, int m)
:time(m) //成员初始化值
{
hour = i;
time = m; //企图赋值,是错误的
//数据初始化
}

void main()
{
const Time T (12, 0, 0); // const对象不能赋值必须初始化
T.getHour(); // 正确,只有const成员函数才能访问const对象
T.setTime(); // 错误,非const函数
}

2.友元类和友元函数
友元函数是在类作用域外定义的,但它有权访问该类的私有成员和受保护成员
在类定义中,在函数原型或类前加入friend,它可放入类中任何地方,一般在类定义开始处
是一种“给予”关系,即B是A的友元,B可访问A中的数据成员及成员函数
class count
{
friend void setX(count & ,int); //声明友元类
public:
private:
int x; //私有数据成员
}

//看到此处了没有,没有用类的类作用域符号,它是类外的一个函数,区别处
void setX(count &c, int val)
{
c.x = val; //友元函数所以可以修改私有数据
}

void main()
{
count c;
setX(c, 8); //用友元函数设置x的值,此处也没用类作用域符,它是类作用外的一个函数
}

3.静态数据成员、函数及对象
* 类的对象通常都有该类的所有数据成员的单独拷贝,为了让对象共享一份拷贝引入静态(static)数据成员.可节省内存。好像全局变量但只有类作用域
* 公有的静态数据成员,可以用双目作用域符通过类名访问Time::count(最好在不存在对象时用),也可用类对象访问 T.count,静态成员函数只能用第二种方式
* 私有的和受保护的只能用公有静态成员函数访问。一个成员函数不要访问类的非静态成员时,声明为静态函数有利于节省内存
* 即使不存在类对象,静态数据成员、函数也存在并可使用。
public:
static int getCount();//静态函数
private:
static int count; //静态成员
int Time::getCount(return count;)

4.模板类(template )
模块类需要一种或多种类型参数,所以模板类也叫带参数的数据类型
声明:template <typename elemType>,模板类外的成员函数也都要以这个形式开头(多长了脑袋--Skyala)
template <typename elemType>
int Stack<elemType>::pop() {}
类名:Stack <elemType>,
实例化:Stack <float> floatStack ,stack为假设的一个模板类对象
多个参数:template <typename felemType,typename selemtype>

5.复合、包容器类、递取类及其他
* 复合: 一个类把另一个作为自己的成员,成员对象是在包括它们的对象之前建立的
成员对象不一定要提供成员初始化值的,其构造函数会自动调用,但如也没默认的构造函数,会出错
* 成员初始化值可避免对成员对象初始化两次(调用默认构造函数及用"set"函数)
Time::Time(int hour, int minute, int second)
:Minute (minute),
Second (second)
{}

6.应用举例:
编译环境:window2000 Vc6.0
a. Stack.cpp 堆栈类

#ifndef TSTACK_H
#define TSTACK_H
template<typename elemType>
class Stack
{
public:
Stack (int = 10); //构造函数,栈默认大小为10
~Stack (); //析构函数
int pop (elemType&); //入栈
int push (const elemType &); //出栈
int isEmpty () const {return _top == -1;} //栈空,返回-1
int isFull () const {return _top == _size - 1;} //栈滿,返回栈顶大小

private:
int _size; //堆栈能容纳元素的数目
int _top; //栈顶元素的数目
elemType * _stackPtr; //指向栈的指针
};

template<typename elemType>
Stack<elemType>::Stack (int s)
{
_size = s;
_top = -1; //空栈
_stackPtr = new elemType [_size];
}

template<typename elemType>
Stack<elemType>::~Stack ()
{ delete [] _stackPtr;}

//压入一元素,如成功返回1,失败返回0
template<typename elemType>
int Stack<elemType>::push (const elemType &item)
{
if (! isFull ())
{
_stackPtr [++_top] = item; //如栈没滿则栈顶赋值后加1
return 1; //成功
}
return 0; //入栈失败
}

//出栈
template<typename elemType>
int Stack<elemType>::pop (elemType &popValue)
{
if (! isEmpty ())
{
popValue = _stackPtr [_top--]; //如栈不为空则赋值后减1
return 1;
}
return 0;
}
#endif

#include <iostream.h>
#include <stdlib.h>

void main ()
{
Stack<int> floatStack (5); //模板实例化
int fS = 1;
cout << "Pushing /n";
while (floatStack.push (fS))
{
cout << fS << ' ';
fS += 1;
}
cout << "Full is:" << fS << '/n' << "Poping /n";

while (floatStack.pop (fS))
{
cout << fS << ' ';
}
cout << endl;

Stack<char> charStack (10);
char cS = 'a';
cout << "Pushing /n";
while (charStack.push (cS))
{
cout << cS << ' ';
cS += 1; //字符串可进行运算
}
cout << "Full is:" << cS << '/n' << "Poping /n";

while (charStack.pop (cS))
{
cout << cS << ' ';
}
cout << endl;
}

程序艺术--没了思想的程序永远不会打动人--天翼(Skyala)