c++ primer plus 第6版 部分三 9章 - 章
第9章 内存模型和名称空间
1.单独编译
组件函数放在独立的文件中。可以单独的编译这些文件,然后链接成可执行的程序。
三部分
a 头文件: 包含结构声明和使用这些结构的函数的原型
b 源代码文件:包含与结构有关的函数的代码
c 源代码文件:调用结构的函数的代码
除非函数为内联函数,否则不要在头文件中包含函数的定义
头文件中通常包含的内容:
函数原型
使用#define或const定义的符号常量
结构声明 (此声明不创建变量)
类声明
模板声明 (指示编译器如何生成函数的定义 本身不会被编译)
内联函数
系统头文件用<> 编译器首先查找标准头文件的主机系统的文件系统
用户自定义的双引号 编译器首先查找当前的工作目录或源代码目录或其他目录。 如果没找到 则在标准位置查找。
小贴士:IDE中,不要将头文件加入到项目列表中,也不要再源代码文件中使用#include来包含其他源代码文件。
同一个文件中同一个头文件只能包含一次。
#ifndef 预处理编译指令
#define coo_h 定义头文件
...
#endif
编译器首次遇到该文件时,名称coo_h 没有定义。在这种情况下,编译器将查看#ifndef和#endif之间的内容。并读取定义 coo_h的一行。如果在同一个文件中遇到其他包含coo_h的代码,编译器将知道coo_h已经被定义了,从而跳到#endif后面的一行上。
多个库的链接
链接的名称 需要一致,但是当不同的编译器创建的二进制模块(对象代码文件)很可能无法正确地链接。两个编译器将为同一个函数生成不同的修饰名称。名成的不同将使连接器无法将一个编译器生成的函数调用与另一个编译器生成的函数定义匹配。在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的。
如果有源代码,通常可以用自己的编译器重新编译源代码来消除链接错误。
2.存储的持续性,作用域和链接性
内存中的变量的存在方式 三种不同的方案来存储数据 区别在与数据保留在内存中的时间
自动存储持续性 例如 函数定义中声明的变量
静态存储持续性 函数定义外定义的变量 和 使用关键字static 定义的变量的存储持续性都为静态 生命周期为整个运行过程
线程存储持续性 多核处理器常见 cpu同时处理多个执行任务 让程序将计算放在可并行处理的不同线程中
动态存储持续性 new运算符分配的内存一直存在,直到使用delete运算符将其释放或程序结束为止。 此时 内存的存储持续性为动态 被称为自由存储或者堆
一.
作用域和链接
作用域:名称在文件的多大范围内可见
链接性:名称如何在不同单元间共享
链接性为外部的名称在文件间共享,链接性为内部的名称由一个文件中的函数共享;
自动变量的名称没有链接性 所以不能共享
作用域种类:局部变量 全局变量
自动变量为局部 静态变量为全局 原型中的参数列表只能在列表的括号中使用
类中的成员为整个类 名称空间中的变量为整个名称空间
函数的作用域可以是整个类或整个名称空间,但不能是局部的,因为不能在代码块内定义函数
自动存储持续性
函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性
{} 代表代码块的开始和结束
变量的作用域从定义它的位置到该代码块的结尾。
内部代码块和外部代码块有名称相同的变量,则取最近的变量。
示例: 执行过程
Int main()
{
Int texas=31; 此时texas变量分配内存空间 变量可见
Oil(texas); //1 1变量位于内存 但是不可见
Return 0;
}//此时1过期 内存释放
Void oil(int x) 执行oil代码 1texas不可见
{
int texas =5; //2 分配空间 且可见
{
Int texas=113; //3 1和2都在内存中,但是不可见;3分配空间 且可见
}//此时3过期 内存释放
}//此时2过期 内存释放
三个texas变量的地址各不相同
1.自动变量初始化 使用任何在声明时其值为已知的表达式来初始化自动变量
Int w=5;
2.自动变量和栈
自动变量的随机出现 所以程序在运行时需要对自动变量进行管理
自动变量的数目随函数的开始和结束而增减。
常用方法为:留出一段内存,并将其视为栈,以管理变量的增减。通常函数将其参数的值放在栈顶。
先进后出
3.寄存器变量
使用cpu寄存器来存储自动变量 提高访问变量的速度
关键字 register只能用于原本就是自动的变量,
静态持续变量
3中链接性: 外部链接性(可在其他文件中访问)、
内部链接性(只能在当前文件中访问)、
和无链接性(只能在当前函数或代码块中访问)
作用域:整个程序运行期间
分配固定的内存来存储所有的静态变量
初始化:未显式的初始化,则将它设置为0
默认情况下,静态数组和结构每个元素或成员的所有位都设置为0.
创建静态持续变量
链接性为外部的静态持续变量,在代码块的外部声明。
链接性为内部的静态持续变量,在代码块的外部声明,并且添加static限定符。
没有链接性的静态持续变量,在代码块内声明它,并使用static限定符。
Int global=1000; //外部静态持续变量
Static int one=50; //内部静态持续变量
Int main(){}
Void func1(int n)
{
Static int count=0; //无链接性持续变量
Int llama=0;
}
Void func2(int q)
{
}
所有静态持续变量在整个程序执行期间都存在。
Funct1()中的变量count的作用域为局部,没有链接性 只能在f函数内部使用
F函数未执行,count也留在内存中
Global和one的作用域都为整个文件,即从声明为位置到文件结尾的范围内都可以被使用。
Global为外部链接性 可以在程序的其他文件中使用
One为内部链接性 只能在包含上述代码的文件中使用
所有未初始化的静态变量的所有位都被设置为0.
静态变量的初始化:
除了0初始化外,还可对静态变量进行常量表达式初始化和动态初始化。
0和常量表达式初始化被称为静态初始化,编译器处理单元时初始化变量
动态初始化意味着变量将在编译后初始化
初始化形式的决定因素:
1.所有静态变量都被零初始化 不论程序员是否显示地初始化
2.常量表达式初始化变量,且编译器仅根据文件内容(包括被包含的头文件)计算表达式,执行常量表达式初始化
#include <cmath>
Int x; 0初始化
Int y=5; 常量表达式 初始化
Long z=13*13; 常量表达式 初始化
Const double pi=4.0*atan(1.0); 动态初始化
首先 x y z被0初始化,然后编译器计算常量表达式 y z分别初始化,但初始化pi 必须调用函数atan()
此时需要等该函数被链接且程序执行时。
常量表达式并非只是使用字面常量的算术表达式,例如sizeof运算符
Int enough=2*sizeof(long)+1;
Constexpr 关键字创建常量表达式
4.静态持续性、外部链接性
链接性为外部的变量通常简称为外部变量,存储持续性为静态,作用域为整个文件。
在所有函数外部定义 可以在之后的所有函数中使用 成为全局变量
4.1 单定义规则
在每个使用外部变量的文件中,都必须声明它;
变量只有一次定义
C++的两种声明变量的方式:1.定义声明 简称定义,它给变量分配存储空间;
2.引用声明 简称声明 不分配空间,仅仅引用已有的变量
引用声明:关键字 extern 且不进行初始化 ; 否则就声明为定义 分配空间
示例:
Double up; 定义,分配空间 初始值为0
Extern int blem; 引用 未分配空间
Extern char gr=’z’; 定义 初始化时分配空间
多文件使用外部变量:在其中一个文件中定义该变量,在其他文件中使用关键字extern来声明。
//File01.cpp
Extern int cats=20; //因为初始化 所以是定义
Int dogs=22; //定义
Int fleas; //定义 以上均分配内存空间
//file02.cpp
Extern int cats; //外部变量 声明 来自file01.cpp
Extern int dogs; //同上
//file03.cpp
Extern int cats; //外部变量 声明 来自file01.cpp
Extern int dogs;
Extern int fleas;
在file01.cpp中关键字 extern可以省略 但其他的文件中不能省略关键字 extern
多个函数中定义的自动变量彼此独立
局部变量隐藏同名的全局变量
函数中一个与外部变量同名的变量 结果是这中声明将被视为一个自动变量的定义。当程序执行自动变量所属的函数时,该变量将位于作用域内。
示例:
9.5 external.cpp
//with support.cpp
#include <iostream>
Using namespace std;
Double warming=0.3; //外部变量的定义 初始化
Void update(double dt);
Void local;
int main( )
{
Cout<<”global warming is”<<warming<<”degrees.\n”;
Update(0.1);
Cout<<”global warming is”<<warming<<”degrees.\n”;
Local();
Cout<<”global warming is”<<warming<<”degrees.\n”;
Return 0;
}
9.6 support.cpp
#include <iostream>
Extern double warming; //引入外部全局变量 来自另一个文件
Void update(double dt);
Void local( );
Using std::cout;
Void update(double dt)
{
Extern double warming; //重新声明 全局变量
Warming+=dt; //改变全局变量
Cout<<”updating global warming to”<<warming; //全局变量
Cout<<”degrees.\n”;
}
Void local()
{
Double warming=0.8; // 新的变量 隐藏了外部引入的变量
Cout<<”local warming=”<<warming<<”degrees.\n”; //局部变量
Cout<<”but global warming = ”<<::warming; //全局变量
Cout<<”degrees.\n”;
}
main()和update()都可以访问外部变量warming
update 使用exter重新声明了warming变量
c++中的作用域解析运算符:: 放在变量名前面时,该运算符表示使用变量的全局版本,而不是自动变量(局部)
全局变量和局部变量:
全局变量易用,但是风险大,数据的完整性不可靠。
局部变量 能够避免对数据进行不必要的访问,就越能保持数据的完整性。
通常使用局部变量,oop的数据隔离
全局变量可以让多个函数共享使用 ,为防止修改 用const来限定其不能修改即可。
示例:
const char * const months[12]=
{
“january”,”february”,”march”,”april”,”may”,...
};
头一个const指示 字符串不可修改;第二个const确保数组中每个指针始终指向它最初指向的字符串。
9.2.5静态持续性、内部链接性
将static限定符用于作用域为整个文件的变量时,该变量的链接性为内部的。
在多文件程序中,内部链接性和外部链接性的差别为:内部的只能在其所属的文件中使用;而外部的变量具有外部链接性,即可以在其他的文件中使用。
示例:
//file1
int errors=20; //external declaration
func1()
{
}
//file2
int errors=5; //定义出错
func2(){}
违反了单定义的规则 file2中定义的外部变量,与之前定义的errors为两个定义,这是错误的
但是如果将file2中的变量加上 static int errors=5; 此时与file1并不矛盾,因为file1中的errors变量的链接性为外部(文件见共享) 而file2中的errors变量的链接性为内部 文件内部共享
在多文件程序中,可以在一个文件中定义一个外部变量,使用该变量的其他文件必须使用关键字extern声明它。
外部变量在多文件程序的不同部分之间共享数据;
链接性为内部的静态变量在同一个文件中的多个函数之间共享数据;
9.7 twofile1.cpp
#include <iostream>
int tom=3; //外部变量定义 外部链接性
int dick=30; //外部变量定义
static int harry=300; //
void remote_access( );
int main( )
{
cout<<&tom<<&harry;
cout<<endl;
remote_access();
return 0;
}
//9.8 twofile2.cpp
#include <iostream>
extern int tom;
static int dick=10;
int harry=200;
void remote_access()
{
using namespace std;
cout<<”remote_access()”<<endl;
cout<<&tom<<&dick<<&harry;
}
两个tom值一样 dick和harry的值均不同
9.2.6 静态存储持续性、无链接性
无链接性的局部变量 将static限定符用于在代码块中定义的变量,此时变量的存储持续性变为静态,意思是:程序运行到代码块时,此变量可用,离开代码块,变量不可用,但是在内存中的空间不消失。两次函数调用之间,静态局部变量的值保持不变。
// 9.9 static.cpp
#include <iostream>
const int arsize=10;
void strcount(const char * str);
int main()
{
using namespace std;
char input[arsize];
char next;
cin.get(input,arsize);
while(cin)
{
cin.get(next);
while(next!='\n')
cin.get(next);
strcount(input);
cout<<"enter next line :\n";
cin.get(input,arsize);
}
cout<<"Bye\n";
return 0;
}
void strcount(const char * str)
{
using namespace std;
static int total=0; // 静态的局部变量 只在第一次执行时初始化, 以后再次执行时不会变为0 在此函数执行完毕后 此变量仍然存在。
int count=0; //自动局部变量 与上面的total的区别 每次执行此函数时,都会初始化为0
while(*str++)
count++;
total+=count;
cout<<count<<"characters\n";
cout<<total<<"characters total\n";
}
9.2.7说明符和限定符
存储说明符
auto 自动类型推断
register 寄存器存储 显式地指出变量是自动的
static 作用域为整个文件时表示内部链接性,而用于局部变量时(代码块或函数时) 表示存储持续性为静态
extern 引用声明 声明引用在其他地方定义的变量
thread_local 持续性与其所属的线程的持续性相同 多核心cpu 类似与常规静态变量与整个程序的关系
mutable 根据const来解释
thread_local和static或者extern结合使用,其他的说明符在同一个声明中不能使用多个说明符。
1.cv-限定符
const 常量 初始化后 程序不能修改其值
volatile 易变的 不稳定的 即使没有对内存单元修改, 其值也可能发生变化
例如程序中的指针指向硬件的地址,硬件中的位置受到硬件的影响可以修改其中的数据,而这些数据又可以被程序访问。 所以两个共享此时的单元数据
此关键字的作用是为了改善编译器的优化能力。
优化的目的是:假设当两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在这两次使用之间不会变化。如果不将变量声明为volatile,则编译器将进行这种优化;将变量声明为volatile 相当于告诉编译器,不要进行这种优化。
2.mutable 易变的 不定性的
即使结构(或类)变量为const 其中某个成员也可以被修改
示例:
struct data
{
char name[30];
mutable int accesses;
...
};
const data veep={"adsfasdf",0,...};
strcpy(veep.name,"joye joux"); //不允许
veep.accesses++; //允许 因为用mutable修饰了
3.再谈const
默认情况下 全局变量的链接性为外部,但是const 全局变量的链接性为内部,c++编译器看来,const定义的全局变量 与static一样
示例:
const int fingers=10; 链接性为内部
int main(void)
{ ...};
c++修改了常量类型的规则,
将一组常规变量放在头文件中,并在同一个程序的多个文件中使用该头文件,那么预处理器将头文件的内容都包含到每个源文件中,所有的源文件都包含如下的代码:
const int fingers=10;
const char * warning="wak!";
如果全局const声明链接性为外部,则根据单定义规则,会出错。只能有一个文件可以包含前面的声明,而其他的文件只能使用extern关键字来提供引用声明。
另 只有extern关键字的声明才能进行初始化
需要为某个文件使用一组定义,而其他文件使用另一组声明。
由于外部定义的const数据的链接性为内部,因此可以在所有文件中使用相同的声明。
内部链接性意味着,每个文件都有自己的一组常量,而不是所有文件共享一组常量。每个定义都是其所属文件私有的。所以可以将常量放在头文件中。这样只要两个源代码文件中包括同一个头文件。则他们将获得同一组常量。
需要将某个常量的链接性设定为外部的 则可以使用extern关键字来覆盖默认的内部链接性
extern const int states=50;
const 类似于static 内部链接性
extern 为导入外部的变量 具有外部链接性 文件间使用
在函数或代码块中定义const时,作用域为代码 名称不冲突。
9.2.8函数和链接性
函数与变量一样 也具有链接性
所有函数的存储持续性都是自动为静态的,即在整个程序执行期间一直存在。
链接性:1.默认为外部 在文件间共享
2.extern关键字指出函数时在另一个文件中定义的,可选。需要在在另一个文件中查找,该文件必须作为程序的组成部分被编译,或者由链接程序搜索的库文件。
3.static 将链接性设置为内部,使之只能在一个文件中使用。必须同时在原型和定义中使用该关键字 static
static具有 内部链接性 所以其他文件可以定义同名的函数 静态函数覆盖外部定义,即使在外部定义了同名的函数 该文件仍将使用静态函数
链接性为外部的函数: 多个文件中只能有一个文件包含该函数的定义,但使用该函数的每个文件都应包含其函数原型。
内联函数 不受这项规则的约束,这允许程序员能将内联函数的定义放在头文件中。
编译器查找函数的路径:
1.如果函数原型表明static 静态,则只在该文件中查找函数定义,
2.没有static 静态声明,编译器和链接程序在所有的程序文件中查找,如果找到两个定义,编译器将发出错误消息,因为每个外部函数只能有一个定义。
3.程序文件中没有,到库中搜索,所以在库中,不能有与标准库相同的函数名,自定义函数名要有所区别,否测出错。
4.有些编译器或链接程序需要显式的指出搜索的库名
9.2.9 语言的链接性
主要针对函数的说明
背景知识:c语言编译器或链接程序在查找函数的名称时,由于c的标识符仅对应一个函数,例如源代码中的spiff() 对应翻译后内部的_spiff,能够保证唯一性,这就是c语言的链接性
c++语言由于有函数重载的出现,所以同一个函数需要对应多个翻译后的内部名称,例如,spiff(int)对应_spoff_i, 而spiff(double,double)转换为_spiff_d_d 这种方法被称为c++语言的链接
c++ 使用c库中预编译的函数 例如spiff(22) //此函数属于c函数库
c库文件中的符号名称为_spiff,但对于链接程序来说,c++查询约定的名称为_spiff_i。所以在程序中有时需要指明使用的是哪种约定 在函数原型中指明
示例: extern "c" void spiff(int); //用c约定来查找
extern void spoff(int); //默认用c++的约定来查找
extern "c++" void spaff(int); // 显式的使用c++来指明
第一个原型 用的是c语言的链接性
第二个原型 c++ 默认
第三个原型 c++ 显式的指明
9.2.10 存储方案和动态分配
1.new 和delete 或者c中的malloc() 称为动态内存,不受作用域和链接性规则的控制或者限制,可以在一个函数中new创建,在另一个函数中delete释放,
前面提到的几种方式是自动内存 执行lifo 先进后出的栈管理模式
而new和delete 为动态内存 不执行lifo 栈模式
2.通常编译器使用三块独立的内存:
静态变量 (可以再细分)
自动变量
动态存储
3.存储方案不适用于动态内存,但是可以用来跟踪动态内存中的自动变量和静态指针变量
float * pfee=new float [20]; //占据80个字节
a new申请内存空间,直到delete释放 ,但是代码块结束 即遇到 } 的时候,pfee指针会消失。
b 代码块外使用该指针的方式一: 将该指针的地址返回或传递给代码块外部的变量
方式二:将指针的链接性声明为外部 则文件中位于该声明的后面的所有函数都可以使用它。
c extern float * pfee //在另一个文件中使用这个声明 就可以使用此指针
有些系统自动释放new创建的内存,而有些操作系统需要手动的用delete来释放内存
1.使用new运算符初始化
初始化动态分配的变量
为内置的标量类型 例如int或者double来分配存储空间并初始化 在类型后面加上初始值
int * pi=new int (6); *pi 设置正6
double * pd=new double(99.99);
struct st1 {double x;double y;double z;}; c++11支持 的列表初始化
st1 * one= new st1{2.5,5.3,7.2}; 结构体初始化
int * ar=new int[4] {2,4,6,7} 整数数组初始化
列表初始化用于单值 而非多值
int * pin=new int{}; *pin
第10章 对象和类
oop为面向对象编程的一种思想 方法 ;可以用于很多语言中 ,包括c++ c等
重要特性为: 抽象;封装和数据隐藏;多态;继承;代码的可重用性
编程时需要考虑如何表示数据 也要考虑如何使用数据;
抽象和类
指定基本类型:
1.决定数据对象需要的内存数量
2.决定如何解释内存中的位 long和float在内存中占用的位数相同,但是转换为数值的方法不同
3.决定可使用数据对象执行的操作或方法
对于内置类型,有关操作的信息被内置到编译器中
c++中的类
类是一种将抽象转换为用户定义类型的c++工具 将数据表示和操纵数据的方法组合成一个整洁的包
接口是一个共享框架 供两个系统交互使用;
对于类,公共接口是使用类的陈故乡,交互系统由类对象组成,接口由编写类的人提供的方法组成。接口让程序员能够编写与类对象交互的代码,从而让程序能够使用类对象。
例如 string类提供的size()的方法,公共用户不能直接访问类,但是公众可以使用方法size()来获取string对象的大小。所以size方法是用户和string类对象之间的公共接口的组成部分 注意是组成部分,而不是全部。
例如 istream类的getline() get()也是类的公共接口的组成部分
案例:
类定义中的成员函数:
获得股票 acquire
增持 buy
卖出股票 sell
更新股票价格 update
显示关于所持股票的信息 show
成员变量:
公司名称 company
所持股票的数量 shares
每股的价格 share_val
股票的总值 total_val
将类的声明放在头文件中
#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 num,double price);
void sell(long num,double price);
void update(double price);
void show();
};
开发一个类并编写一个程序 需要多个步骤,这里是将接口(类定义) 放在头文件中的第一步。
之后会将实现(类方法的代码)放在源代码文件中。 上例中是Stock类的类声明。
1.上例中的访问控制
private public protected
public是类以外的程序可以直接访问的共有部分,
private只能通过公有成员函数来访问对象的私有成员 修改shares 只能使用stock类中的成员函数 另友元函数也可访问
protected 受保护的成员
类设计将共有接口和实现细节分开,公有接口表示设计的抽象组件。
封装:将实现细节放在一起并将它们与抽象分开被称为封装。
数据隐藏:封装一种,将实现细节隐藏在私有部分中。
设计类的成员函数需要注意的是:类的使用者无需了解数据是如何被表示的。但是需要知道成员函数接受的参数以及返回什么类型的值。原则是将实现细节从接口设计中分离出来,再维护更新的过程中如果有更好的函数方法,可以修改细节,而无需修改程序的接口 ,维护更加的容易
2.控制对成员的访问:公有还是私有
隐藏数据是oop主要的目标之一,因此数据项通常放在私有部分,组成类接口的成员函数放在公有部分。
通常 程序员使用私有成员函数来处理不属于公有接口的实现细节。
不必在类声明中使用关键字 private 因为这是类对象的默认访问控制:
class world
{ // 默认为private 隐式的
float mass;
char name[20];
public:
void tellall(void);
...
}
类和结构体的区别:
结构的默认访问类型是public 而类为private c++程序员通常使用类来实现类描述,而把结构限制为只表示纯粹的数据对象
POD 普通老式数据
实现类成员函数
类描述的第二部分
为类声明中的原型表示的成员函数提供代码。
1.定义成员函数时,使用作用域解析运算符 :: 来标识函数所属的类;
2.类方法可以访问类的private组件
例如:void stock::update(double price)
作用域解析运算符确定了方法定义对应的类的身份。我们说标识符update具有类作用域。其余的类中也可以使用update函数名
类方法的完整名称中包括类名, stock::update 是函数的限定名;而简单的update是全名的缩写 仅仅在类作用域中使用
独立的实现文件如下所示:
//stock00.cpp
#include <iostream>
#include "stock00.h"
void stock::acquire(const std::string & co,long n,double pr ) //成员函数的实现 获得股票的函数实现,参数包括公司名称 数量 价格
{
company=co;
if(n<0)
{
std::cout<<"number of shares can't be negative;"<<company<<"shares set to 0.";
shares=0;
}
else
shares=n;//股票设置为n
share_val=pr;//设置股票价格
set_tot();//设置总价
}
void stock::buy(long num,double price)//买进股票 参数为数量和价格
{
if(num<0)
{
std::cout<<"number of shares purchased can't be negative."<<"transaction is aborted.\n";
}
else
{
shares+=num;//更改股票数量
share_val=price;//更改股票价格
set_tot(); //设置股票总价
}
}
void stock::sell(long num,double price)//卖出 数量和价格
{
using std::cout;
if(num<0)
{
cout<<"you can't sell more than you hanve!"<<"transaction is aborted.\n";
}
else
{
shares-=num; //修改股票数量
share_val=price;//修改价格
set_tot();//设置总价
}
}
void stock::update(double price)//更新股票价格
{
share_val=price;//更新价格
set_tot(); //设置总价
}
void stock::show() //显示
{
stdLLcout<<"company:"<<company //股票名称
<<"shares:"<<shares<<'\n' //股票总数
<<"share price:%$"<<share_val //股票价格
<<"total worth:$"<<total_val<<'\n'; //股票总价
}
以上示例的说明:
1.acquire()函数管理对某个公司股票的首次购买 buy和sell 为增加或减少持有的股票。
total_val 成员值 每个函数都调用set_tot()只是实现代码的一种方式,而不是公有接口的组成部分,因此这个类将其声明为私有成员函数(即编写这个类的人可以使用它,但编写代码来使用这个类的人不能使用)。
2.内联方法
定义为与类声明中的函数都将自动成为内联函数 stock::set_tot()是一个内联函数。类声明常将短小的成员函数作为内联函数
也可以在类声明之外定义成员函数 ,并使其成为内联函数。为此 只需要在类实现部分中定义函数时使用inline限定符即可:
class stock
{
private:
...
void set_tot(); 声明中定义的函数
public:
...
};
inline void stock::set_tot() //内联函数的实现 定义在类的声明之外
{
total_Val=shares*share_val;
}
内联函数 要求在每个使用它们的文件中都对其进行定义。
确保内联定义对多文件程序中的所有文件都可用的方法是:将内联定义放在定义类的头文件中
根据改写规则:在类声明中定义方法等同于用原型替换方法定义,然后在类声明的后面将定义改写为内联函数。
方法使用的对象选择 也就是说如何将类的方法应用于对象
stock kate,joe; 声明了两个对象
kate.show(); 用对象调用函数
joe.show();
根据类所创建的每个对象都有自己的存储空间,用于存储器内部变量和类成员;但是同一个类的所有对象共享同一组类方法,即每中方法只有一个副本。
例如,假设kate和joe都是stock对象,则kate.shares将占据一个内存块,而joe.share占用另一个内存块,但是kate和joe的show方法都调用同一个方法,也就是执行同一个代码块。
oop中调用成员函数称为发送消息,将同样的消息发送给两个不同的对象将调用同一个方法,但该方法被用于两个不同的对象。
使用类
示例:
#include <iostream>
#include "stock00.h"
int main()
{
stock fluffy_the_cat; //定义一个类对象
fluffy_the_cat.acquire("nummm",20,21.5);
return 0;
}
客户服务器模型
客户为使用类的程序,类声明包括类中的方法构成了服务器。客户只能通过公有方式定义的接口来使用服务器。
所以客户只需要了解接口的用法
服务器设计人员需要确保服务器中的类设计中的实现细节。
修改实现
1.格式的修改 避免科学计数法
std::cout.setf(std::ios_base::fixed,std::ios_base::floatfield);
这设置了cout对象的一个标记,命令cout使用定点表示法
std::cout.precision(3);
2.恢复调用前的状态
std::streamsize prec=std::cout.precision(3); //保存 当前设置的值
...
std::cout.precision(prec); //设置成原来的值
std::ios_base::fmtflags orig=std::cout.setf(std::ios_base::fixed); //存储原始的值
...
std::cout.setf(orig,std::ios_base::floatfield); //重新设置成存储的值
修改show函数中的代码
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); //设置成小数点格式后面3位
cout<<"company:"<<company<<“shares”<<shares<<'\n';
cout<<"share price:$"<<share_val;
cout.precision(2);//小数点后面2位有效位
cout<<"total worth:$"<<total_val<<'\n';
cout.setf(orig,ios_base::floatfield);//恢复现场 原来的数据
cout.precision(prec);
}
10.3类的构造函数和析构函数
常规变量的初始化 int a=32;
类的对象不能用此方法初始化:需要用到类的构造函数来初始化 原因是类中的数据的某些部分是私有的。程序不能直接访问数据成员。程序只能通过成员函数来访问数据成员
创建对象的时候由类的特殊的成员函数--构造函数来初始化对象
3.1声明和定义构造函数
原型:
stock(const string & co,long n=0,double pr=0.0);
定义:
Stock::stock(const string & co,long n,double pr)
{
company-co;
...
}
小贴士:成员名和构造函数中的参数名 不能够重复
通常的做法是成员名有前缀或者后缀,参数名不变
例如:
构造函数中的参数:Stock::Stock(const string & company,long shares,double share_val)
{ }
成员名:private:
string m_company;
long m_shares
或者
private:
string company_;
long shares_;
使用构造函数:
1.显式调用
stock food=stock("world cabbage",250,1.25); 此种方式为长格式
2.隐式调用
stock garment("furry mason",50,2.5);
3.利用new动态的创建对象初始化
stock * pstock=new stock("electroshock games",18,19.0);
创建的对象没有名称 只有一个指针指向此匿名对象
注意无法用对象来调用构造函数 当使用构造函数之前,对象是不存在的。
3.3 默认构造函数
概念:默认构造函数为编译器自动提供的,用来初始化对象的成员函数
出现的时机:程序员未提供任何的构造函数,则c++将自动的提供默认的构造函数;
未提供显式的初始值时,用来创建对象的构造函数
stock fluffy_the_cat; 此时调用默认的构造函数
声明:stock::stock(){}
定义默认构造函数的方式:
1. 给已有的构造函数的所有参数提供默认值
stock(const string & co="error",int n=0,double pr=0.0);
2.通过函数重载来定义另一个构造函数 一个没有参数的构造函数
stock();
一个程序中只能有一个构造函数,所以不要同时使用1和2
通常对所有类成员做隐式初始化的默认构造函数
示例:
stock firest; 隐式调用默认构造函数
stock first=stock(); 显式的调用默认构造函数
stock * prelief= new stock; 隐式的调用默认构造函数
stock first("concrete conglomerate") 调用构造函数 此函数为非默认构造函数 因为有参数
stock second(); 声明一个函数 返回类型为stock类型
stock third; 调用默认构造函数 注意不能有括号
3.4析构函数
对象的生命周期,创建时用构造函数,消除时用析构函数
如果构造函数用new来分配内存,则析构函数使用delete来释放这些内存
声明方法: ~stock();
或者 stock::~stock(){}; 可以没有任何操作
调用时机:
通常不显式地调用析构函数,
1.创建的是静态存储类对象,则在程序结束时自动被调用。
2.如果创建的是自动存储类对象,则在执行完代码块时自动调用。
3.如果是new创建的 ,则它驻留在栈内存或自由存储区, 后进先出的调用析构函数来调用delete释放对象
同默认构造函数:如果程序员没有提供一个析构函数,则编译器将隐式地声明一个默认的析构函数。
3.5 编写程序的流程
1.头文件:包括类的声明以及构造函数和析构函数的原型。包括public和private部分
宏定义:
#ifndef STOCK10_H_
#define STOCK10_H_
...
#endif
2.实现文件:包括成员函数的实现方法
文件名为:implestock.cpp
#include "stock10.h" //包含之前定义的头文件
stock::stock() //默认的构造函数
{ }
stcok::stock(const std::string & co,long n,double pr)
{...} //程序员定义的带参
...其他的成员函数的实现
3.使用类的程序文件 客户文件
#include "stock10.h"
#include "implestock.cpp"
int main()
{
{
using std::cout;
stock stock1;
stock stock2;
...
} //大括号的作用是在此时调用析构函数 如果没有这里的大括号,则代码块将是整个main 所以main执行完毕后,才会调用析构函数
return 0;
}
3.6 初始化示例
1.stock stock1("nanosmart",12,20.0);
创建一个名字为stock1的对象
2.stock stock2=stock("boffo",2,20.0);
长格式创建 第一种方式与1完全相同
第二中方式为先创建一个临时的对象,然后将该临时对象复制到stock2中,并丢弃它。
3.对象的赋值
stock2=stock1
4.构造函数用来赋值 而不是初始化
stock1=stock("nifty",10,50.0); 构造函数创建一个新的临时的对象 然后复制给stock1 再删除临时的对象
5.自动变量为于栈中,所以析构函数顺序保持后进先出
3.7 c++11支持
c++列表初始化
与构造函数的参数列表匹配即可,使用大括号
stock jock={"asdf",10,45.0}; 匹配三个参数的构造函数
stock jock{"asdf"}; 匹配三个参数但是后两个为默认参数的构造函数
stock jock{}; 匹配没有参数的默认构造函数
注意std::initialize_list 的类,用于函数参数或方法参数的类型
3.8 const成员函数
cosnt stock land=stock("asdfasdf");
land.show(); 错误
show的代码无法确保调用对象过程中不会修改对象中的数据
所以需要将const关键字放在函数的括号后面 来保证不被修改
void show() const
函数的原型和函数的实现头都应该为:void stock::show() const
这类函数就是const成员函数 所以类中的方法如果不修改调用对象,就应该将其声明为const。