C++格式化输出(转)
标准输出函数cout :
/*关于浮点数的格式*/
#include <iostream.h>
void main()
{
float f=2.0/3.0,f1=0.000000001,f2=-9.9;
cout<<f<<' '<<f1<<' '<<f2<<endl; //正常输出
cout.setf(ios::showpos); //强制在正数前加+号
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout.unsetf(ios::showpos); //取消正数前加+号
cout.setf(ios::showpoint); //强制显示小数点后的无效0
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout.unsetf(ios::showpoint); //取消显示小数点后的无效0
cout.setf(ios::scientific); //科学记数法
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout.unsetf(ios::scientific); //取消科学记数法
cout.setf(ios::fixed); //按点输出显示
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout.unsetf(ios::fixed); //取消按点输出显示
cout.precision(18); //精度为18,正常为6
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout.precision(6); //精度恢复为6
}
操纵算子:
/*关于浮点数的格式*/
#include <iomanip.h>
void main()
{
float f=2.0/3.0,f1=0.000000001,f2=-9.9;
cout<<f<<' '<<f1<<' '<<f2<<endl; //正常输出
cout<<setiosflags(ios::showpos); //强制在正数前加+号
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout<<resetiosflags(ios::showpos); //取消正数前加+号
cout<<setiosflags(ios::showpoint); //强制显示小数点后的无效0
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout<<resetiosflags(ios::showpoint); //取消显示小数点后的无效0
cout<<setiosflags(ios::scientific); //科学记数法
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout<<resetiosflags(ios::scientific); //取消科学记数法
cout<<setiosflags(ios::fixed); //按点输出显示
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout<<resetiosflags(ios::fixed); //取消按点输出显示
cout<<setprecision(18); //精度为18,正常为6
cout<<f<<' '<<f1<<' '<<f2<<endl;
cout<<setprecision(6); //精度恢复为6
}
1.打印浮点数控制位数
Java代码
setf: set flag
#incluce <iomanip>// 操作元manipulator
cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout.precision(2); //用两位小数显示
cout<< your_double_variable<<endl;
cout.precision(5); //再次改变时只需加这一句就可以了。
cout::setf(ios::showpos);//正数显示时加正号
cout::setf(ios::scientific);//用科学计数法表示
cout::setf(ios::left);//左对齐
cout::setf(ios::right);//右对齐
cout.width(4);//只对一次有用
cout<<setw(4)<<4<<endl;//控制输出宽度
cout.unsetf(ios::showpos);//取消设置标志位
setf: set flag
#incluce <iomanip>// 操作元manipulator
cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout.precision(2); //用两位小数显示
cout<< your_double_variable<<endl;
cout.precision(5); //再次改变时只需加这一句就可以了。
cout::setf(ios::showpos);//正数显示时加正号
cout::setf(ios::scientific);//用科学计数法表示
cout::setf(ios::left);//左对齐
cout::setf(ios::right);//右对齐
cout.width(4);//只对一次有用
cout<<setw(4)<<4<<endl;//控制输出宽度
cout.unsetf(ios::showpos);//取消设置标志位
2.不同类型占的位数:
long double 12个字节:sizeof(long double) = 12;//devc++
double 8个字节 :sizeof(double)=8;//devc++
short int 2字节:sizeof(short int) = 2;//devc++
variable op= expression <=> variable = variable op (expression);
3.取余数问题:
正整数%负整数=正整数
负整数%正整数=负整数
负整数%负整数=负整数
4.重载:
当定义一个这样的两个函数时
Java代码
double sum(int a, int b)
{
return a + b;
}
double sum(double a, double b)
{
return a + b;
}
double sum(int a, int b)
{
return a + b;
}
double sum(double a, double b)
{
return a + b;
}当调用
Java代码
sum(2, 2.5);
sum(2, 2.5); 时编译器会报错:
error: call of overloaded ‘mp(int, double)’ is ambiguous
5.调试时的技术:
stub和驱动程序。
stub程序是一个占位函数,比如说要测试一个函数,而这个函数又调用了另一个函数(这个函数还没有编写),那么这个时候我们可以写一下这个函数,在这个函数中写一个简单的返回值或根据需要制造一个数据。
驱动程序就是写一个函数来调用被测试函数从而测试函数。
6.assert的用法:
assert的作用是当一个表达式的值为false时进行报告并终止程序。
Java代码
#include<assert>
assert(Expression);
#include<assert>
assert(Expression);
当程序中出现很多assert时,我们想去掉了怎么办,很简单,在#include<assert>前加上#define NDEBUG即可
Java代码
#define NDEBUG
#include <assert>
#define NDEBUG
#include <assert>
7.exit
Java代码
#include <cstdlib>
正常退出:
exit(0);
否则:
exit(1);
#include <cstdlib>
正常退出:
exit(0);
否则:
exit(1);
8.流操作
文件流:
Java代码
#include <fstream>
#include <cstdlib>
ifstream in_stream;
ofstream out_stream;
in_stream.open("read_file_name"); //关联文件
out_stream.open("out_file_name");
if(in_stream.fail()) //判断是否打开了
{
cout<<"in file open failed!"<<endl;
exit(1);
}
if(out_stream.fail()) //判断是否打开了
{
cout<<"out file open failed!"<<endl;
exit(1);
}
in_stream.close();
out_stream.close();
追加文件:
in_stream.open("infile", ios::app);
ifstream ofstream 派生于iostream所以可以这样写用到流的地方:
void hello(istream& cin, ostream& out)
{
}
void hello(ifstream& cin, ofstream& out)
{
}
用第一种写成的函数,可以传递文件流,当没有流参数时默认用iostream
#include <fstream>
#include <cstdlib>
ifstream in_stream;
ofstream out_stream;
in_stream.open("read_file_name"); //关联文件
out_stream.open("out_file_name");
if(in_stream.fail()) //判断是否打开了
{
cout<<"in file open failed!"<<endl;
exit(1);
}
if(out_stream.fail()) //判断是否打开了
{
cout<<"out file open failed!"<<endl;
exit(1);
}
in_stream.close();
out_stream.close();
追加文件:
in_stream.open("infile", ios::app);
ifstream ofstream 派生于iostream所以可以这样写用到流的地方:
void hello(istream& cin, ostream& out)
{
}
void hello(ifstream& cin, ofstream& out)
{
}
用第一种写成的函数,可以传递文件流,当没有流参数时默认用iostream
9.string
string类有两个构造函数:一个是可以初始化为一个空字符串,另一个接收字符数组,将字符数组转换成string
Java代码
cin.getline(char_array);
cin.get(char_var);
getline(cin, string_var);//第一种录入一行
getline(cin, string_var, '?');//第二种遇到指定字符结束
string 的一些函数:
str.at(i) 等价于str[i],但是会检查是否越界,更安全
str.substr(position, length);//返回调用对象的一个子字符串,它起始于position,总共包含length个字符
str.empty();//如果是空返回true,否则返回false
str.insert(pos, str2);//
str.remove(pos, length);//
str.c_str();//string转换成字符串
比较:
==, != , > , >= , < , <=
查找:
str.find(str1);//返回str1在str中首次出现时的索引
str.find(str1, pos);//返回str1在str中首次出现时的索引,从位置pos处开始查找
str.find_first_of(str1, pos);//返回str1的【【任何字符】】在str中首次出现时的索引,从位置pos处开始查找
str.find_first_not_of(str1, pos);//返回不属于str1的【【任何字符】】在str中首次出现时的索引,从位置pos处开始查找
cin.getline(char_array);
cin.get(char_var);
getline(cin, string_var);//第一种录入一行
getline(cin, string_var, '?');//第二种遇到指定字符结束
string 的一些函数:
str.at(i) 等价于str[i],但是会检查是否越界,更安全
str.substr(position, length);//返回调用对象的一个子字符串,它起始于position,总共包含length个字符
str.empty();//如果是空返回true,否则返回false
str.insert(pos, str2);//
str.remove(pos, length);//
str.c_str();//string转换成字符串
比较:
==, != , > , >= , < , <=
查找:
str.find(str1);//返回str1在str中首次出现时的索引
str.find(str1, pos);//返回str1在str中首次出现时的索引,从位置pos处开始查找
str.find_first_of(str1, pos);//返回str1的【【任何字符】】在str中首次出现时的索引,从位置pos处开始查找
str.find_first_not_of(str1, pos);//返回不属于str1的【【任何字符】】在str中首次出现时的索引,从位置pos处开始查找
10.vector
Java代码
#include <vector>
using namespace std;
vector 添加元素:v.push_back();
vector只能更改一个已经赋值的元素。
可以初始化由参数指定的位置数目:
vector<int> v(10);
为构造函数提供哦你一个整数参数时,向量会初始化成相应数据类型的零值。如果向量的基类型是一个类,就会用那个类的默认构造函数来进行初始化。
向量的长度和容量:
向量的长度是向量中元素的个数,容量则是当前实际分配了内存的元素个数。
v.size();
v.capacity();
改变大小:
v.reserve(32);//到达不少于32的容量
v.resize(32);//调整到32, 多的删掉,少的增加。
#include <vector>
using namespace std;
vector 添加元素:v.push_back();
vector只能更改一个已经赋值的元素。
可以初始化由参数指定的位置数目:
vector<int> v(10);
为构造函数提供哦你一个整数参数时,向量会初始化成相应数据类型的零值。如果向量的基类型是一个类,就会用那个类的默认构造函数来进行初始化。
向量的长度和容量:
向量的长度是向量中元素的个数,容量则是当前实际分配了内存的元素个数。
v.size();
v.capacity();
改变大小:
v.reserve(32);//到达不少于32的容量
v.resize(32);//调整到32, 多的删掉,少的增加。
11. 字符串转数字
Java代码
#include <stdlib.h>
atoi();//转换成整数
atol();//转换成长整型
atof();//转换成浮点,double
#include <stdlib.h>
atoi();//转换成整数
atol();//转换成长整型
atof();//转换成浮点,double
12.const使用时注意
Java代码
一个在形参声明为const的变量在函数内部调用其他函数时,如果用到这个变量,那么将要调用的函数对此参数也要声明为const
一个在形参声明为const的变量在函数内部调用其他函数时,如果用到这个变量,那么将要调用的函数对此参数也要声明为const
13. new动态变量和数组
Java代码
p = new int;//动态变量
delete p ;
p = new int[10];//动态数组
delete []p;//释放,删除数组
注意:int* p1, p2;实际上声明了一个p1 为int型指针,p2为int型变量。
而
typedef int* IntPtr;
IntPtr p1, p2;//则声明了两个指针。
数组变量实际是指针变量:
IntPtr p;
int a[10];
p = a;
但区别是不可更改数组变量中的指针值。
例:a = p2//非法
p = new int;//动态变量
delete p ;
p = new int[10];//动态数组
delete []p;//释放,删除数组
注意:int* p1, p2;实际上声明了一个p1 为int型指针,p2为int型变量。
而
typedef int* IntPtr;
IntPtr p1, p2;//则声明了两个指针。
数组变量实际是指针变量:
IntPtr p;
int a[10];
p = a;
但区别是不可更改数组变量中的指针值。
例:a = p2//非法
14.多维动态数组
Java代码
typedef int* IntArrayPtr;
IntArrayPtr *m = new IntArrayPtr[3];
m[i] = new int[4];
删除多维数组:
delete []m[i];
delete []m;
typedef int* IntArrayPtr;
int main()
{
IntArrayPtr *m = new IntArrayPtr[3];
for(int i = 0; i < 3; i++)
m[i] = new int[4];
for(int i = 0; i < 3; i++)
for(int j = 0 ; j < 4; j++)
m[i][j] = i * j;
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 4; j++)
cout << m[i][j]<<" ";
cout<<endl;
}
for(int i = 0; i < 3; i++)
delete []m[i];
delete []m;
return 0;
}
typedef int* IntArrayPtr;
IntArrayPtr *m = new IntArrayPtr[3];
m[i] = new int[4];
删除多维数组:
delete []m[i];
delete []m;
typedef int* IntArrayPtr;
int main()
{
IntArrayPtr *m = new IntArrayPtr[3];
for(int i = 0; i < 3; i++)
m[i] = new int[4];
for(int i = 0; i < 3; i++)
for(int j = 0 ; j < 4; j++)
m[i][j] = i * j;
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 4; j++)
cout << m[i][j]<<" ";
cout<<endl;
}
for(int i = 0; i < 3; i++)
delete []m[i];
delete []m;
return 0;
}
15.struct
Java代码
struct{};
千万别忘了加‘;’
结枸体用作参数是否是引用传递或者是传值调用
struct{};
千万别忘了加‘;’
结枸体用作参数是否是引用传递或者是传值调用
16.类中成员默认为private
17.假如构造函数没有参数,就不要在对象声明中包括圆括号。
18.匿名对象:是指一个尚未由任何变量命名的对象再次调用构造函数
19.不能重载的操作符:
Java代码
. //成员访问运算符
.* //成员指针访问运算符
:: //域运算符
sizeof //长度运算符
?: //条件运算符
. //成员访问运算符
.* //成员指针访问运算符
:: //域运算符
sizeof //长度运算符
?: //条件运算符
20.头文件的编译技巧
Java代码
#ifndef YOURHEADER_H
#define YOURheADER_H
.....
#endif//防止重定义多次包含
#ifndef YOURHEADER_H
#define YOURheADER_H
.....
#endif//防止重定义多次包含
21.用于自动类型转换的构造函数??
Java代码
Money(5) + 25
自动转换成Money类???还不明白
Money(5) + 25
自动转换成Money类???还不明白
22.重定义和重载不同??
Java代码
重载是函数名称一样,参数类型不同,个数不同
重定义是名称,个数,类型一样。
重载是函数名称一样,参数类型不同,个数不同
重定义是名称,个数,类型一样。
23.构造两次,析构四次的问题。
代码如下:
Java代码
#include <iostream>
#define NDEBUG
#include <cassert>
#include <iomanip>
using namespace std;
double mp(int a, int b)
{
cout<< "this is from int mp." << endl;
return a + b;
}
double mp(double a, double b)
{
cout<< "this is from double mp." << endl;
return a + b;
}
class Money
{
private:
double i;
public:
Money(double t){
this->i = t;
cout<< "constructor" << t <<endl;
};
Money(){
cout<< "default constructor" <<endl;
}
friend Money operator + ( Money& a, Money& b);
double get()
{
return this->i;
}
friend ostream& operator << (ostream& outs, Money b);
friend istream& operator >> (istream& ins, Money& b);
~Money()
{
cout<< "destructor" << this->i <<endl;
}
};
Money operator + (Money& a, Money& b)
{
return Money(a.i + b.i);
}
ostream& operator << (ostream& outs, Money b)
{
outs<<b.i<< "overloadding cout" <<endl;
return outs;
}
istream& operator >> (istream& ins, Money& b)
{
int t;
ins>>t;
b.i = t;
return ins;
}
int main()
{
cout<<"c++ is ok now!"<<endl;
Money a, b;
cin>>a>>b;
cout<<a<<b;
//Money b = a + 3.0;
//a + 3;
// cout<<"dd "<< b.get()<<endl;
return 0;
}
#include <iostream>
#define NDEBUG
#include <cassert>
#include <iomanip>
using namespace std;
double mp(int a, int b)
{
cout<< "this is from int mp." << endl;
return a + b;
}
double mp(double a, double b)
{
cout<< "this is from double mp." << endl;
return a + b;
}
class Money
{
private:
double i;
public:
Money(double t){
this->i = t;
cout<< "constructor" << t <<endl;
};
Money(){
cout<< "default constructor" <<endl;
}
friend Money operator + ( Money& a, Money& b);
double get()
{
return this->i;
}
friend ostream& operator << (ostream& outs, Money b);
friend istream& operator >> (istream& ins, Money& b);
~Money()
{
cout<< "destructor" << this->i <<endl;
}
};
Money operator + (Money& a, Money& b)
{
return Money(a.i + b.i);
}
ostream& operator << (ostream& outs, Money b)
{
outs<<b.i<< "overloadding cout" <<endl;
return outs;
}
istream& operator >> (istream& ins, Money& b)
{
int t;
ins>>t;
b.i = t;
return ins;
}
int main()
{
cout<<"c++ is ok now!"<<endl;
Money a, b;
cin>>a>>b;
cout<<a<<b;
//Money b = a + 3.0;
//a + 3;
// cout<<"dd "<< b.get()<<endl;
return 0;
}
运行结果如下:
Java代码
c++ is ok now!
default constructor
default constructor
2
3
2overloadding cout
3overloadding cout
destructor2
destructor3
destructor3
destructor2
c++ is ok now!
default constructor
default constructor
2
3
2overloadding cout
3overloadding cout
destructor2
destructor3
destructor3
destructor2
24.对象知识
Java代码
类的友元函数本质上是一个普通函数,只是它能像成员函数那样访问类的私有成员
如果您的类有一套完整的取值函数,那么将是一个函数变成有元的惟一理由就是使有元函数的定义更简单,更有效,但这些理由通常已经足够了。
重载操作符<<和>>时,返回的类型应该时一个流类型,而且返回的类型必须是一个引用(需要为返回类型的名称附加一个后缀 &)
可为一个类重载赋值操作符=,使它具有正常的行为。然而,它必须作为类的一个成员来重载,而不能作为一个友元来重载。使用了指针和new操作符的任何类都应该重载用于那个类的赋值操作符。
命名空间是名称定义的一个集合,这些名称定义包括类定义和变量声明等。
无名命名空间使一个名称定义对于一个编译单元来说是”局部“的。
函数签名
函数的签名是指函数名以及参数类表中的类型序列,但不包括关键字const和&,以这个对签名的定义为准,重载一个函数名称时,函数名称的两个定义必须具有不同的签名。
在派生类中,如果一个函数与基类中的一个函数同名,但具有不同的签名,就是重载而非重定义。
访问重定义的基函数:
Employee jann_e;
HourlyEmployee sally_h;
jane_e.print_check();
sally_h.print_check();
sally_h.Employee::print_check();
不继承的函数:
构造函数,析构函数,私有成员函数,复制构造函数,赋值操作符=也是不继承的。
派生类的析构函数:
派生类的析构函数会自动调用基类的构造函数。
假设有B派生自A,C派生自B,那么调用构造函数时的顺序是:A,B,C;
调用析构函数的顺序是:C,B,A和构造函数正好相反。
多态性:
晚期绑定是指在运行时判断因该使用成员函数的哪一个版本。c++利用虚函数来实现晚期绑定。多态性是指借助晚期绑定技术,为一个函数名关联多种含义的能力。因此,多态性、晚期绑定和虚函数 其实是一个主题。
基类中的proteted成员可在公共派生类的成员函数中直接访问。
基类中在函数上声明了virtual,在派生类中不用在声明,默认继承为virtual,但为了避免混淆,我们在派生列继承时写上关键子virtual,是一种良好的编程习惯。
派生类、基类的赋值:可以将派生类的值赋值给基类,但不能相反,基类没有的会自动丢弃(切片问题)。
编程提示:虚析构函数。最好总是将析构函数标记为虚函数。例如:
Base *pBase = new Derived;
...
delete pBase;
此时会调用Base的析构函数,假如Derived中有动态变量,那么就不会被释放,直到程序结束。但如果声明为virtual,delete时就会调用Derived的析构函数,从而不会出现问题。
throw 语句:
throw Expression_for_Value_to_Be_Thrown;
try-throw-catch是抛出和捕获异常的基本机制。
可以有多个throw和catch,没有参数的异常类,只写类型名也就是类名即可。
例如:
class DivideByZero{};//空类
int main()
{
try{
throw DivideByZero();
}
catch(DivedeByZero)
{
SomeCode...
}
}
捕获多个异常时,catch块的顺序可能十分重要 。try块中抛出一个异常值之后,会依次常识后续的catch块,与抛出的异常的类型相匹配的第一个catch块会得以执行。处理原则是先处理较为具体的异常。
编程提示:异常类可能写的很简单,微不足道。
例如:calss DivideByZero{};
但它确可以抛出异常,将控制权交给catch代码块。
异常规范:
如果存在可能抛出的异常,但是在函数定义中没有捕捉,那么应该在一个异常规范中列出那些异常类型
double safe_divide(int top, int bottom) throw (DivideByZero);
异常规范应该同时函数声明和函数定义中。如果一个函数具有多个函数声明,那么所有函数声明都必须有完全一致的异常规范。函数的异常规范有时也称为throw列表。
如果一个函数定义中可能抛出多个异常,就哟你格斗号来分割不同的异常类型。
doubel some_functon() throw (DivideByZero, OtherException);
如果没有任何异常规范(throw列表),就连一个空白的异常规范都没有,那么效果等同于在异常符ifanzhog自动列出了所有可能的异常类型;换言之,抛出的任何异常都会得到正常的处理。
那么,假如在函数中抛出一个异常,但异常规范中并未列出这个异常(也没有在函数内部捕捉),会发生什么情况呢?在这种情况下,程序会终止。尤其要注意的是,假如一个异常在函数中抛出,但既没有在异常规范中列出,也没有在函数内部捕捉,那么它不会被任何catch块捕捉,而是直接导致程序终止。记住,如果完全没有异常规范列表,就连空白的都没有,那么效果等同于在规范列表中列出所有异常。有这种情况下,抛出一个异常不会终止程序。
注意:异常规范是为那些准惫跑到函数外部的异常而准惫的。如果它们的下不跑到函数外部,就不归入异常规范。
要表示一个函数不应该抛出任何不在函数内部捕捉的异常,需要使用一个空白屏异常规范,如下所示:
void some_function() throw();
几种方式可以总结如下:
void some_function() throw(DivideByZero, OtherException);
//DivideByZero 或OtherException 类型的异常会被正常处理
//至于其他任何异常,如果抛出后未在函数主体中捕捉,就会终止程序
void some_function() throw();
//空异常列表;一旦抛出任何未在函数主体中捕捉的异常,就会终止程序
void some_function();
//正常处理所有类型的所有异常
派生类中重定义或覆盖一个函数定义时,它应具有与基类中一样的异常规范,或至少应该在新的异常规范中给出基类异常规范的一个子集。换言之,重定义或覆盖一个函数定义时,不可在异常规范中添加新异常。但是,如果愿意,可删减基类中原有的异常。之所以有这个要求,是因为在能够使用基类对象的任何地方,都能使用一个派生类对象。因此,重定义或覆盖的函数必须兼容于为基类对象编写称的任何代码。
许多编译器都不支持模板函数声明,也不支持模板函数的独立编译。所以一个比较保险的策略就是根本不使用模板函数声明,而且确保函数模板定义位于使用它的同一个文件中,而且确保定义位于它的使用位置之前。
模板可以用于函数,也可以用于类。
迭代器量指针的泛化形式。迭代器用于在一个容器的某个区间中遍历元素。通常为迭代器定义了++,--和*(提领)等操作。
迭代器的分类:
正向迭代器:(forward iterator):++适用于这种迭代器。
双向迭代器:(bidirectional iterator):++和—都适用于这种迭代器
随机访问迭代器(random access iterator):++ ,--和随机访问都适用于这种迭代器。
这三种迭代器又分为常量和可变迭代器。还有逆向迭代器
reverse_iterator rp;
for(rp = container.rbegin(); rp != container.rend(); rp++)//这里++是反向的。
{
some_code;
}
类的友元函数本质上是一个普通函数,只是它能像成员函数那样访问类的私有成员
如果您的类有一套完整的取值函数,那么将是一个函数变成有元的惟一理由就是使有元函数的定义更简单,更有效,但这些理由通常已经足够了。
重载操作符<<和>>时,返回的类型应该时一个流类型,而且返回的类型必须是一个引用(需要为返回类型的名称附加一个后缀 &)
可为一个类重载赋值操作符=,使它具有正常的行为。然而,它必须作为类的一个成员来重载,而不能作为一个友元来重载。使用了指针和new操作符的任何类都应该重载用于那个类的赋值操作符。
命名空间是名称定义的一个集合,这些名称定义包括类定义和变量声明等。
无名命名空间使一个名称定义对于一个编译单元来说是”局部“的。
函数签名
函数的签名是指函数名以及参数类表中的类型序列,但不包括关键字const和&,以这个对签名的定义为准,重载一个函数名称时,函数名称的两个定义必须具有不同的签名。
在派生类中,如果一个函数与基类中的一个函数同名,但具有不同的签名,就是重载而非重定义。
访问重定义的基函数:
Employee jann_e;
HourlyEmployee sally_h;
jane_e.print_check();
sally_h.print_check();
sally_h.Employee::print_check();
不继承的函数:
构造函数,析构函数,私有成员函数,复制构造函数,赋值操作符=也是不继承的。
派生类的析构函数:
派生类的析构函数会自动调用基类的构造函数。
假设有B派生自A,C派生自B,那么调用构造函数时的顺序是:A,B,C;
调用析构函数的顺序是:C,B,A和构造函数正好相反。
多态性:
晚期绑定是指在运行时判断因该使用成员函数的哪一个版本。c++利用虚函数来实现晚期绑定。多态性是指借助晚期绑定技术,为一个函数名关联多种含义的能力。因此,多态性、晚期绑定和虚函数 其实是一个主题。
基类中的proteted成员可在公共派生类的成员函数中直接访问。
基类中在函数上声明了virtual,在派生类中不用在声明,默认继承为virtual,但为了避免混淆,我们在派生列继承时写上关键子virtual,是一种良好的编程习惯。
派生类、基类的赋值:可以将派生类的值赋值给基类,但不能相反,基类没有的会自动丢弃(切片问题)。
编程提示:虚析构函数。最好总是将析构函数标记为虚函数。例如:
Base *pBase = new Derived;
...
delete pBase;
此时会调用Base的析构函数,假如Derived中有动态变量,那么就不会被释放,直到程序结束。但如果声明为virtual,delete时就会调用Derived的析构函数,从而不会出现问题。
throw 语句:
throw Expression_for_Value_to_Be_Thrown;
try-throw-catch是抛出和捕获异常的基本机制。
可以有多个throw和catch,没有参数的异常类,只写类型名也就是类名即可。
例如:
class DivideByZero{};//空类
int main()
{
try{
throw DivideByZero();
}
catch(DivedeByZero)
{
SomeCode...
}
}
捕获多个异常时,catch块的顺序可能十分重要 。try块中抛出一个异常值之后,会依次常识后续的catch块,与抛出的异常的类型相匹配的第一个catch块会得以执行。处理原则是先处理较为具体的异常。
编程提示:异常类可能写的很简单,微不足道。
例如:calss DivideByZero{};
但它确可以抛出异常,将控制权交给catch代码块。
异常规范:
如果存在可能抛出的异常,但是在函数定义中没有捕捉,那么应该在一个异常规范中列出那些异常类型
double safe_divide(int top, int bottom) throw (DivideByZero);
异常规范应该同时函数声明和函数定义中。如果一个函数具有多个函数声明,那么所有函数声明都必须有完全一致的异常规范。函数的异常规范有时也称为throw列表。
如果一个函数定义中可能抛出多个异常,就哟你格斗号来分割不同的异常类型。
doubel some_functon() throw (DivideByZero, OtherException);
如果没有任何异常规范(throw列表),就连一个空白的异常规范都没有,那么效果等同于在异常符ifanzhog自动列出了所有可能的异常类型;换言之,抛出的任何异常都会得到正常的处理。
那么,假如在函数中抛出一个异常,但异常规范中并未列出这个异常(也没有在函数内部捕捉),会发生什么情况呢?在这种情况下,程序会终止。尤其要注意的是,假如一个异常在函数中抛出,但既没有在异常规范中列出,也没有在函数内部捕捉,那么它不会被任何catch块捕捉,而是直接导致程序终止。记住,如果完全没有异常规范列表,就连空白的都没有,那么效果等同于在规范列表中列出所有异常。有这种情况下,抛出一个异常不会终止程序。
注意:异常规范是为那些准惫跑到函数外部的异常而准惫的。如果它们的下不跑到函数外部,就不归入异常规范。
要表示一个函数不应该抛出任何不在函数内部捕捉的异常,需要使用一个空白屏异常规范,如下所示:
void some_function() throw();
几种方式可以总结如下:
void some_function() throw(DivideByZero, OtherException);
//DivideByZero 或OtherException 类型的异常会被正常处理
//至于其他任何异常,如果抛出后未在函数主体中捕捉,就会终止程序
void some_function() throw();
//空异常列表;一旦抛出任何未在函数主体中捕捉的异常,就会终止程序
void some_function();
//正常处理所有类型的所有异常
派生类中重定义或覆盖一个函数定义时,它应具有与基类中一样的异常规范,或至少应该在新的异常规范中给出基类异常规范的一个子集。换言之,重定义或覆盖一个函数定义时,不可在异常规范中添加新异常。但是,如果愿意,可删减基类中原有的异常。之所以有这个要求,是因为在能够使用基类对象的任何地方,都能使用一个派生类对象。因此,重定义或覆盖的函数必须兼容于为基类对象编写称的任何代码。
许多编译器都不支持模板函数声明,也不支持模板函数的独立编译。所以一个比较保险的策略就是根本不使用模板函数声明,而且确保函数模板定义位于使用它的同一个文件中,而且确保定义位于它的使用位置之前。
模板可以用于函数,也可以用于类。
迭代器量指针的泛化形式。迭代器用于在一个容器的某个区间中遍历元素。通常为迭代器定义了++,--和*(提领)等操作。
迭代器的分类:
正向迭代器:(forward iterator):++适用于这种迭代器。
双向迭代器:(bidirectional iterator):++和—都适用于这种迭代器
随机访问迭代器(random access iterator):++ ,--和随机访问都适用于这种迭代器。
这三种迭代器又分为常量和可变迭代器。还有逆向迭代器
reverse_iterator rp;
for(rp = container.rbegin(); rp != container.rend(); rp++)//这里++是反向的。
{
some_code;
}
25. typedef 小结 <!-- @page { margin: 2cm } PRE { font-family: "DejaVu Sans" } P { margin-bottom: 0.21cm } -->
Java代码
归纳起来,声明一个新的类型名的方法是:
(1)先将定义变量的方法写出定义语句(如 int i)
(2)将变量名换成新类型名(如将i换成COUNT)
(3)在最前面加typedef(如typedef int COUNT)
(4)然后可以用新类型名去定义变量
再以声明上述的数组类型为例来说明:
(1)先定义数组形式书写:int n[100]
(2)将变量名n换成自己指定的类型名:int n[100]
(3)在前面辊上typedef,得到typedef int NUM[100]
(4)用来定义变量:NUM n;(n是包含100个整数组)
归纳起来,声明一个新的类型名的方法是:
(1)先将定义变量的方法写出定义语句(如 int i)
(2)将变量名换成新类型名(如将i换成COUNT)
(3)在最前面加typedef(如typedef int COUNT)
(4)然后可以用新类型名去定义变量
再以声明上述的数组类型为例来说明:
(1)先定义数组形式书写:int n[100]
(2)将变量名n换成自己指定的类型名:int n[100]
(3)在前面辊上typedef,得到typedef int NUM[100]
(4)用来定义变量:NUM n;(n是包含100个整数组)
26.一个整数不能直接赋值给一个枚举类型变量,可以强制转换来实现。
<!-- @page { margin: 2cm } PRE { font-family: "DejaVu Sans" } P { margin-bottom: 0.21cm } -->
27. 对象知识(续)
Java代码
类是对象的抽象,而对象是类的具体实例。
c++允许用struct来定义一个类类型。
Inline成员函数:
在类体中定义的成员函数的规模一般都比较小,而系统调用函数的过程所花费的时间开销相对是比较大的。为了减小时间开销,如果在类体中定义的成员函数中不包括循环竺控制结构,c++系统会自动将它们作为内置函数来处理。
应该注意的是:如果成员函数不在类体内定义,而在类体外定义,系统并不把它默认为内因此函数,调用这些成员函数的过程和调用一般的函数的过程是相同的。如果想将这些成员函数指定这内轩函数,应当用inline作显示声明。
在进行异常处埋后,程序并不会自动终止,继续执行catch子句后面的语句。
如果在catch子句中没有指定异常信息的类型,而用了删节号”...”,则表示它可以捕捉任何类型的异常信息。如:catch(...){}
这种catch子句应放在try-catch结构中的最后,相当于“其他”。如果把它作为第一个catch了句,则后面的catch子句都不起作用。
如果throw抛出的异常信息找不到与之匹配的catch块,那么系统会调用一个系统函数terminate,使程序终止运行。
程序会在异常处理中处理析构函数。具体是在将异常变量赋值后处理析构函数。
使用命名空间成员的方法:
(1)使用命名空间别名:
namespace Television{}
namespace TV = Television;
(2)使用using命名空间成员名
using ns1::Student;
(3)使用using namespace 命名空间名
using namespace 命名空间名
无名的命名空间:
相当于static的用法,将作用域限定在本文件中。
实际上,在内存中为每一个数据流开辟一个内存缓冲区,用来存放流中有数据。当用cout和插入运算符“<<”向显示器输出数据时,先将这些数据送到程序中的输出缓冲区保存,直到缓冲区满了或遇到endl,就将缓冲区中有全部数据送到显示器显示出来。在输入时,从键盘输入有数据先放在键盘有缓冲区中,当按回车时,键盘缓冲区中的数据输入到程序中的输入缓冲区,形成cin流,然后用提取运算符“>>”,从输入缓冲区中提取数据送给程序中的有关变量。总之,流是与内存缓冲区相对应的,或者说,缓冲区中的数据就是流。
cout 是console output的缩写,cout不是c++预定义的关键字,它是ostream流类的对象,在iostream中定义。
cerr是不经过缓冲区,直接向显示器上输出有关信息,而clog中的信息存放在缓冲区中,缓冲区满后或遇endl时向显示器输出。
流成员函数 |与之作用相同的控制符 | 作用
precision(n) setprecision(n) //设置实数的精度为n位
width(n) setw(n) //设置字段宽度为n位
fill(c) setfill(c) //设置填充字符c
setf() setiosflags() //设置输出格式状态,括号中应给出格式状态,
//内容与控制符setiosf lags括号中的内容相同
unsetf() resetioflags(); //终止已设置的输出格式状态,在括号中就应指定内容
int a;
cout<< "input a:";
cin>> a;
cout<<"dec"<<dec<<a<<endl;
cout<<"hex"<<hex<<a<<endl;
cout<<"oct"<<setbase(8)<<a<<endl;
char *pt = "China";
cout<<setw(10)<<pt<<endl;
cout<<setfill('*')<<setw(10)<<pt<<endl;
double pi = 22.0/7.0;
cout<<setiosflags(ios::scientific)<<setprecision(8);
cout<<pi<<endl;
cout<<"pi="<<setprecision(4)<<pi<<endl;
cout<<"pi="<<setiosflags(ios::fixed)<<pi<<endl;
三种get
while((c=cin.get) != EOF)
cout.put(c);
while(cin.get(c))
cout.put(c);
char a[10];
cin.get(a, 10, '\n');//最多接受9个字符,或者遇到'\n'结束
利用输入输出字符串流对象可以作为临时的存储空间。类似于sscanf,sprintf;
从系统实现的角度看,从态性分为两类:静态多态性和动态性。以前学的函数重载和运算符重载实现的多态属于静态多态性。
纯虚函数与抽象类:
virtual float area()=0;//纯虚函数
凡是包含纯虚函数的类都是抽象类。
派生类的构造函数的执行顺序:基类构造函数,子对象,派生类新成员。
虚基类并不是在声明基类时声明的,而是在声明派生类时声明的。因为一个基类可以在生成一个数据采集生类时作为虚在类,而在生成另一个数据采集生类时不作为虚基类。声明虚基类的一般形式为:
class 派生类名: virtual 继承方式 基类名
虚基类的派生类中只保留一份基类的成员。
虚基类的派生类的构造函数初始化:在最后的派人生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
this->n 也可以写成 (*this).n
定义一个变量的别名:
int a;
int& aa = a;// aa 就是a的别名
一个变量的引用就是变量的别名。
用于类对象的运算符一般必须重载,但有两个例外,致富算符“=”和“&”不必用户重载。
运算符重载函数可以是类的成员函数,也可以是类的友函数,还可以是既非类的成员函数也不是友元函数的普通函数。
重载单目运算符:++,--
重载前置++时是classname operator::operator++();
后置++时是classname operator::operator++(int);
对象转换成其他类型:
成员函数:
operator double(){return a_real_num;}//没有类型
如果建立时选用无参构造函数,就不要写()。
例:
Box box1;//正确
Box box1();//错误,相当于声明了一个函数
一个类可以有多个构造函数,但是只能有一个析构函数。
如果用户没有定义析构函数,以c++会自动生成一个析构函数,但它只是徒有析构函数的名称和形式,实际上什么操作都不进行。想让析构函数完成任何工作,都必须在定义的析构函数中指定。
常量对象成员:
例:
const int hour;
构造函数应这样写:
Time(int n):hour(n){};//类内部函数不用写“;”
指向对象的常指针:将指向对象的指针变量声明为const型,并使之初始化,这样指针值始终保持为初始值,不能改变,即其指向始终不变。
类名 * const 指针变量名 = 对象地址。
指向常对象的指针变量:const 类型名 * 指针变量名;
对象的常引用: int fun(const class_name &t)
静态数据成员:静态数据成员不属于某一个对象,在为对象所分配的空间中不包括静态数据成员所占的空间。它不随对象的撤消而释放,到程序的结束才释放。静态数据成员可以初始化,但只能在类体外进行初始化。
int Box::height = 10;
静态数据成员既可以通过对象名引用,也可以通过类名来引用。
静态成员函数:静态成员函数没有this指针。
友元成员函数,友元类:
friend B;//友元类
注意:(1)友元的关系是单向的而不是多向的(2)友元的关系不能传递。
class Money
{
private:
double i;
public:
Money(double a, double b)
{
i = a + b;
cout<<"two parameter constructor"<<endl;
}
Money(double t){
i = t;
cout<< "conversation constructor" << t <<endl;
};
Money(){
cout<< "default constructor" <<endl;
}
friend Money operator + (Money &a, Money& b);
double get()
{
return i;
}
friend ostream& operator << (ostream& outs, Money& b);
friend istream& operator >> (istream& ins, Money& b);
~Money()
{
cout<< "destructor" << this->i <<endl;
}
};
Money operator + (Money& a, Money& b)
{
return Money(a.i + b.i);
}
ostream& operator << (ostream& outs, Money& b)
{
outs<<b.i<< "overloadding cout" <<endl;
return outs;
}
istream& operator >> (istream& ins, Money& b)
{
int t;
ins>>t;
b.i = t;
return ins;
}
int main()
{
cout<<"c++ is ok now!"<<endl;
Money a(4,5);
Money b = a + 5.5;
cout<<a<<b;
//Money b = a + 3.0;
//a + 3;
// cout<<"dd "<< b.get()<<endl;
return 0;
}
//compile error
g++ hello.cpp -o hello
hello.cpp: In function ‘int main()’:
hello.cpp:70: error: no match for ‘operator+’ in ‘a + 5.5e+0’
hello.cpp:49: note: candidates are: Money operator+(Money&, Money&)
make: *** [hello] 错误 1
将money operator+ (Money a, Money b)就可以了,为什么?
类是对象的抽象,而对象是类的具体实例。
c++允许用struct来定义一个类类型。
Inline成员函数:
在类体中定义的成员函数的规模一般都比较小,而系统调用函数的过程所花费的时间开销相对是比较大的。为了减小时间开销,如果在类体中定义的成员函数中不包括循环竺控制结构,c++系统会自动将它们作为内置函数来处理。
应该注意的是:如果成员函数不在类体内定义,而在类体外定义,系统并不把它默认为内因此函数,调用这些成员函数的过程和调用一般的函数的过程是相同的。如果想将这些成员函数指定这内轩函数,应当用inline作显示声明。
在进行异常处埋后,程序并不会自动终止,继续执行catch子句后面的语句。
如果在catch子句中没有指定异常信息的类型,而用了删节号”...”,则表示它可以捕捉任何类型的异常信息。如:catch(...){}
这种catch子句应放在try-catch结构中的最后,相当于“其他”。如果把它作为第一个catch了句,则后面的catch子句都不起作用。
如果throw抛出的异常信息找不到与之匹配的catch块,那么系统会调用一个系统函数terminate,使程序终止运行。
程序会在异常处理中处理析构函数。具体是在将异常变量赋值后处理析构函数。
使用命名空间成员的方法:
(1)使用命名空间别名:
namespace Television{}
namespace TV = Television;
(2)使用using命名空间成员名
using ns1::Student;
(3)使用using namespace 命名空间名
using namespace 命名空间名
无名的命名空间:
相当于static的用法,将作用域限定在本文件中。
实际上,在内存中为每一个数据流开辟一个内存缓冲区,用来存放流中有数据。当用cout和插入运算符“<<”向显示器输出数据时,先将这些数据送到程序中的输出缓冲区保存,直到缓冲区满了或遇到endl,就将缓冲区中有全部数据送到显示器显示出来。在输入时,从键盘输入有数据先放在键盘有缓冲区中,当按回车时,键盘缓冲区中的数据输入到程序中的输入缓冲区,形成cin流,然后用提取运算符“>>”,从输入缓冲区中提取数据送给程序中的有关变量。总之,流是与内存缓冲区相对应的,或者说,缓冲区中的数据就是流。
cout 是console output的缩写,cout不是c++预定义的关键字,它是ostream流类的对象,在iostream中定义。
cerr是不经过缓冲区,直接向显示器上输出有关信息,而clog中的信息存放在缓冲区中,缓冲区满后或遇endl时向显示器输出。
流成员函数 |与之作用相同的控制符 | 作用
precision(n) setprecision(n) //设置实数的精度为n位
width(n) setw(n) //设置字段宽度为n位
fill(c) setfill(c) //设置填充字符c
setf() setiosflags() //设置输出格式状态,括号中应给出格式状态,
//内容与控制符setiosf lags括号中的内容相同
unsetf() resetioflags(); //终止已设置的输出格式状态,在括号中就应指定内容
int a;
cout<< "input a:";
cin>> a;
cout<<"dec"<<dec<<a<<endl;
cout<<"hex"<<hex<<a<<endl;
cout<<"oct"<<setbase(8)<<a<<endl;
char *pt = "China";
cout<<setw(10)<<pt<<endl;
cout<<setfill('*')<<setw(10)<<pt<<endl;
double pi = 22.0/7.0;
cout<<setiosflags(ios::scientific)<<setprecision(8);
cout<<pi<<endl;
cout<<"pi="<<setprecision(4)<<pi<<endl;
cout<<"pi="<<setiosflags(ios::fixed)<<pi<<endl;
三种get
while((c=cin.get) != EOF)
cout.put(c);
while(cin.get(c))
cout.put(c);
char a[10];
cin.get(a, 10, '\n');//最多接受9个字符,或者遇到'\n'结束
利用输入输出字符串流对象可以作为临时的存储空间。类似于sscanf,sprintf;
从系统实现的角度看,从态性分为两类:静态多态性和动态性。以前学的函数重载和运算符重载实现的多态属于静态多态性。
纯虚函数与抽象类:
virtual float area()=0;//纯虚函数
凡是包含纯虚函数的类都是抽象类。
派生类的构造函数的执行顺序:基类构造函数,子对象,派生类新成员。
虚基类并不是在声明基类时声明的,而是在声明派生类时声明的。因为一个基类可以在生成一个数据采集生类时作为虚在类,而在生成另一个数据采集生类时不作为虚基类。声明虚基类的一般形式为:
class 派生类名: virtual 继承方式 基类名
虚基类的派生类中只保留一份基类的成员。
虚基类的派生类的构造函数初始化:在最后的派人生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
this->n 也可以写成 (*this).n
定义一个变量的别名:
int a;
int& aa = a;// aa 就是a的别名
一个变量的引用就是变量的别名。
用于类对象的运算符一般必须重载,但有两个例外,致富算符“=”和“&”不必用户重载。
运算符重载函数可以是类的成员函数,也可以是类的友函数,还可以是既非类的成员函数也不是友元函数的普通函数。
重载单目运算符:++,--
重载前置++时是classname operator::operator++();
后置++时是classname operator::operator++(int);
对象转换成其他类型:
成员函数:
operator double(){return a_real_num;}//没有类型
如果建立时选用无参构造函数,就不要写()。
例:
Box box1;//正确
Box box1();//错误,相当于声明了一个函数
一个类可以有多个构造函数,但是只能有一个析构函数。
如果用户没有定义析构函数,以c++会自动生成一个析构函数,但它只是徒有析构函数的名称和形式,实际上什么操作都不进行。想让析构函数完成任何工作,都必须在定义的析构函数中指定。
常量对象成员:
例:
const int hour;
构造函数应这样写:
Time(int n):hour(n){};//类内部函数不用写“;”
指向对象的常指针:将指向对象的指针变量声明为const型,并使之初始化,这样指针值始终保持为初始值,不能改变,即其指向始终不变。
类名 * const 指针变量名 = 对象地址。
指向常对象的指针变量:const 类型名 * 指针变量名;
对象的常引用: int fun(const class_name &t)
静态数据成员:静态数据成员不属于某一个对象,在为对象所分配的空间中不包括静态数据成员所占的空间。它不随对象的撤消而释放,到程序的结束才释放。静态数据成员可以初始化,但只能在类体外进行初始化。
int Box::height = 10;
静态数据成员既可以通过对象名引用,也可以通过类名来引用。
静态成员函数:静态成员函数没有this指针。
友元成员函数,友元类:
friend B;//友元类
注意:(1)友元的关系是单向的而不是多向的(2)友元的关系不能传递。
class Money
{
private:
double i;
public:
Money(double a, double b)
{
i = a + b;
cout<<"two parameter constructor"<<endl;
}
Money(double t){
i = t;
cout<< "conversation constructor" << t <<endl;
};
Money(){
cout<< "default constructor" <<endl;
}
friend Money operator + (Money &a, Money& b);
double get()
{
return i;
}
friend ostream& operator << (ostream& outs, Money& b);
friend istream& operator >> (istream& ins, Money& b);
~Money()
{
cout<< "destructor" << this->i <<endl;
}
};
Money operator + (Money& a, Money& b)
{
return Money(a.i + b.i);
}
ostream& operator << (ostream& outs, Money& b)
{
outs<<b.i<< "overloadding cout" <<endl;
return outs;
}
istream& operator >> (istream& ins, Money& b)
{
int t;
ins>>t;
b.i = t;
return ins;
}
int main()
{
cout<<"c++ is ok now!"<<endl;
Money a(4,5);
Money b = a + 5.5;
cout<<a<<b;
//Money b = a + 3.0;
//a + 3;
// cout<<"dd "<< b.get()<<endl;
return 0;
}
//compile error
g++ hello.cpp -o hello
hello.cpp: In function ‘int main()’:
hello.cpp:70: error: no match for ‘operator+’ in ‘a + 5.5e+0’
hello.cpp:49: note: candidates are: Money operator+(Money&, Money&)
make: *** [hello] 错误 1
将money operator+ (Money a, Money b)就可以了,为什么?