类背后蕴含的思想是数据抽象和封装


数据抽象和封装的好处
1,避免类内部出现无意的、可能破坏对象爱你个状态的用户级错误。
2,随时间推移可以根据需求改变或缺陷报告来完美类实现,而无需改变用户级代码类的定义已分号结束。分号是必需的,因为在类定义之后可以接一个对象定义列表。定义必须已分号结束。通常,将对象定义成类定义的的一部分是一个坏主意。这样做,会使所发生的操作难以理解。对读者而言,将两个不同的实体(类和变量)组合在一个语句中,也会令人迷惑不解。隐含的this指针


可变数据成员
我们希望类的数据成员(甚至在const成员函数内)可以修改,。这可以通过将他们声明为mutable实现。
可变数据成员永远都不能为const,甚至当他是const对象的成员时也是如此。因此,const成员函数可以改变


mutable成员。要将数据成员声明为可变的,必须将关键


字mutable放在成员声明之前。
class Screen{
public:
private:
multable size_t s;
};


void Screen::do_display(std::ostream& os)const
{
++s;
os<<contents;
}
尽管do_display是const,它也可以增加s。该成员是可变成员,所以,任意成员函数,包括const函数,都可以改变s的值


设计良好的程序通常具有想do_display这样的小函数,
他们被调用来完成其他函数的"实际"工作


类定义实际上是在两个阶段中处理
1,首先,编译成员声明
2,只有在所有成员出现之后,才编译它们本身


与其他函数不一样,构造函数具有名字,形参表和函数体。与其他函数不同的是,构造函数也可以包含一个构


造函数初始化列表
Sales_item::Sales_item(const string &book):isbn


(book),units_sold(0),revenue(0.0){}


构造函数初始化列表以一个冒号开始,接着是一个以逗
号分隔的数据成员列表,每个数据成员后面跟一个放在
圆括号中的初始化式。这个构造函数将isbn成员初始化
为book形参的值,将units_sold和revenue初始化为0。
与任意的成员函数一样,构造函数可以定义在类的内部
或外部。构造函数初始化只在构造函数的定义中而不是
声明中指定。


构造函数初始化列表是许多相当有经验的C++程序员都没
有掌握的一个特性


记住,可以初始化const对象或引用类型的对象,但不能
对它们赋值。在开始执行构造函数体之前,要完成初始
化。初始化const或引用类型数据成员的唯一机会是构造


函数初始化列表中
ConstRef::ConstRef(int ii):i(ii),ci(i),ri(ii){};
下面的构造函数是错误的:
class ConstRef{
public: 
int i;
const int ci;
int &ri;
};
ConstRef::ConstRef(int ii)
{
i = ii;//ok
ci = ii;//error,cannot assign to a const
ri = i;//assign to ri which was not 


bound to an object;
}


成员初始化次序
每个成员在构造函数初始化列表中只能指定一次,这不
会令人惊讶。也许更令人惊讶的是,构造函数初始化列
表仅指定用于初始化成员的值,并不指定这些初始化执
行的次序。成员被初始化的次序就是成员定义的次序。


第一个成员首先被初始化,然后是第二个,依此类推。
初始化的次序常常无关紧要。然而,如果一个成员是根
据其他成员而初始化,则成员初始化的次序是至关重要
的。


string isbn;
tian():isbn(10,'9'){};
这个初始化式使用string构造函数,接受一个计数值和


一个字符,并声称一个string,来保存重复指定次数的
字符


class Sales_item{
Sales_item(const std::string &book = 


""):isbn(book),units_sold(0){};
Sales_item(std::istream &is);

}
Sales_item empty;
Sales_item Primer_3rd_Ed("abc");


对于分别接受一个string和接受一个istream&的构造函
数,具有默认实参都是合法的吗?如果不是,为什么。
当定义时为指定参数无法确定调用哪一个


一个类哪怕只定义了一个构造函数,编译器也不会再生
成默认构造函数。这条规则的根据是,如果一个类在某
种情况下需要控制对象初始化,则该类很可能在所有情
况下都需要控制


只有当一个类没有定义构造函数时,编译器才会自动生
成一个默认构造函数


合成的默认构造函数使用与变量初始化相同的规则来初
始化成员。具有类类型的成员通过运行各自的默认构造


函数来进行初始化。内置和复合类型的成员,如指针和
数组,只对定义在全局作用域中的对象才初始化。当对
象定义在局部作用域中时,内置或复合类型的成员不进
行初始化


如果类包含内置或复合类型的成员,则该类不应该依赖

于合成的默认构造函数。它应该定义自己的构造函数来

初始化这些成员



实际上,如果定义了其他构造函数,则提供一个默认构
造函数几乎总是对的。通常,在默认构造函数中给成员
提供的初始值应该指出该对象是“空”的。


使用默认构造函数应该去掉后面的();
例如
Sales_item myobj();//ok;but define a 


function,not a object;
正确做法应该是 
Sales_item myobj;
下面的这段代码也是正确的
Sales_item myobj = Sales_item();


隐式类型转换
对于下面的代码
class Sales_item{
Sales_item(const std::string &book=""):isbn


(book),units_sold(0),revenue(0.0){}
Sales_item(std::istream &is);
}
这里的每个构造函数都定义了一个隐式转换。因此,在


期待一个Sales_item类型对象的地方,可以使用一个


string或一个istream:
string null_book = "tianhongzeng";
item.same_isbn(null_book);
这段程序使用一个string类型对象作为实参传给
Sales_item的same_isbn函数。该函数期待一个
Sales_item对象作为实参。编译器使用接受一个string
的Sales_item构造函数从null_book生成一个新的
Sales_item对象。新生成的Sales_item被传递给
same_isbn;
这个行为是否是我们想要的,依赖于我们认为用户将如
何使用这个转换。在这种情况下,它可能是一个好主意
。book中的string可能代表一个不存在的ISBN,对
same_isbn的调用可以检测item中的Sales_item是否表示
一个空的Sales_item。另一方面,用户也许在null_book


上错误地调用了same_isbn。
更成问题的是istream到Sales_item的转换:
item.same_isbn(cin);
这段代码将cin隐式转换为Sales_item。这个转换执行接
受一个istream的Sales_item构造函数。该构造函数通过
读取标准输入来创建一个临时Sales_item对象。然后该
对象被传递给same_isbn。
这个Sales_item对象是一个临时对象。一旦same_isbn结
束。就不能访问它。实际上我们构造了一个测试完成后
被丢弃的对象。这个行为几乎肯定是一个错误。


抑制由构造函数定义的隐式转换
可以通过将构造函数声明为explicit,来防止需要隐式


转换的上下中使用构造函数:
class Sales_item{
public:
explicit Sales_item(const std::string &book = 


""):isbn(book),units_sold(0),revenue(0.0){}
explicit Sales_item(std::istream &is);
}


explicit 关键字只能用于内部类的构造声明上。在类定
义体外不所做的定义不能重复它。


现在,两个构造函数都不能用于隐式地创建对象。前两
个使用都不能编译:
item.same_isbn(null_book);//error:string 


constructor is explicit
item.same_isbn(cin);//error:istream constructor 


is explicit


为转换而显示地使用构造函数
string null_book = "tianhongzeng";
item.same_isbn(Sales_item(null_book));


通常,除非有明显的理由想要定义隐式转换,否则,单
行参构造函数应该为explicit。将构造函数设置为
explicit可以避免错误,并且当转换有用时,用户可以
显示地构造对象。


类成员的显示初始化
尽管大多数对象可以通过运行适当的构造函数进行初始
化,但是直接初始化简单的非抽象类的数据成员仍是可
能的。对于没有定义构造函数并且其全体数据成员均为
public的类,可以采用与出事话数组元素相同的方式初


始化其成员:
struct Date{
int ival;
char *ptr;
};
Data vall = {0,0};
Date val2 = {1024,"tian hong zeng"};
根据数据成员的声明次序来使用初始化式。例如,因为
ival在ptr之前声明,所以下面的用法是错误的:
Date val2 = {"tian hong zeng",1024};


显示初始化类类型有三个重大的缺点。
要求类的全体数据成员都是public
将初始化每个对象的每个成员的负担都放在程序员身上
。这样的初始化是乏味且易出错,因为容易遗忘初始化
式或提供不适当的初始化式。
如果增加或删除一个成员,必须找到所有的初始化并正
确更新


定义和使用构造函数几乎总是比较好的。当我们为自己
定义的类型提供一个默认构造函数时,允许编译器自动
运行那个构造,以保证每个类对象在初次使用之前正确
地初始化


友员
在某些情况下,允许特定的非成员函数访问一个类的私
有成员,同时仍然阻止一般的访问,这是很方便做到的

友员机制允许一个类将对其非公有成员的访问权授予制


定的函数或类。友员的声明以关键字friend开始。它只
能出现在类定义的内部。友员声明可以出现在类中的任
何地方:友员不是授予友员关系的那个类的成员,所以


它们不受声明出现部分的访问控制影响
通常将友员声明成组地放在类定义的开始或结尾是个好
主意。


class Screen{
friend class Window_Mgr;
}
Window_Mgr的成员可以直接引用Screen的私有成员。
Window_Mgr&
Window_Mgr::relocate(Screen::index r, 


Screen::index c, Screen &s)
{
s.height += r;
s.width +=c;
return *this.
}


使其他类的成员函数成为友员
class Screen{
friend Window_Mgr& Window_Mgr::relocate


(Window_Mgr::index,Windowx_Mgr::index,Screen&);
}
当我们将成员函数声明为友员时,函数名必须用该函数


所属的类名字加以限定


友员声明与作用域
为了正确地构造类,需要注意友员声明与友员定义之间
的相互依赖。在前面的例子中,类Window_Mgr必须先定
义。否则,Screen类将不能将一个Window_Mgr函数指定
为友员。然而,只有定义类Screen之后,才能定义
relocate函数--毕竟,它被设定为友员是为了访问类


Screen的成员。

更一般地讲,必须先定义包含成员函数的类,才能将成
员函数设为友员。另一方面,不必预先声明类和非成员
函数来将它们设为友员。
友员声明将已命名的类或非成员函数引入到外围作用域
中。此外,友员函数可以在类的内部定义,该函数的作
用域扩展到包围该类的作用域
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值