单继承
继承性是面向对象程序设计中最重要的机制。这种机制提供了无限重复利用程序资源的一种途径。通过C++语言中的集成机制,可以扩展和完善旧的程序设计与适应新的需求。这样不仅可以节省程序开发的时间和资源,并且为未来程序增添了新的资源。
class studet
{
int num;
char name[30];
char sex;
public:
void display()//对成员函数display的定义
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
}
};
class student1
{
int num;//此行原来已有
char name[30];//此行原来已有
char sex;//此行原来已有
int age;
chae addr[20];
public:
void display()//对成员函数display的定义
{
cout << "num:" << num << endl;//此行原来已有
cout << "name:" << name << endl;//此行原来已有
cout << "sex:" << sex << endl;//此行原来已有
cout << "age:" << age << endl;
cout << "address:" << addr << endl;
}
};
class Student1:public Student//声明基类是Student
{
private:
int age;//新增加的数据成员
string addr;//新增加的数据成员
public:
void display_1()//新增加的成员函数
{
cout << "age:" << age << endl;
cout << "address:" << addr << endl;
}
};
利用原来定义的类Student作为基础,再加上新的内容即可,以减少重复的工作量。C++提供的继承机制就是为了解决这个问题。
在C++中所谓“继承”就是在一个已存在的类的基础上建立一个新的类。已存在的类称为基类(base class)或父类(father class)。新建立的类称为派生类(derived class)或子类(son class)。
类A派生为类B:类A为基类,类B为派生类。
在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,同时还拥有旧的成员我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新的类称为派生类,由称为子类。
在建立派生类的过程中,基类不会作任何改变,派生类则除了继承基类的所有可引用的成员变量和成员函数外,还可另外定义本身的成员变量和处理这些变量的函数,由于派生类可继承基类的成员变量和成员函数,因此在基类中定义好的数据和函数等的程序代码可重复使用,这样可以提高程序的可靠性。
当从已有的类中派生出新的类时,可以对派生类做以下几种变化:
1. 可以继承基类的成员数据或成员函数
2. 可以增加新的成员变量
3. 可以增加新的成员函数
4. 可以重新定义已有的成员函数
5. 可以改变现有的成员属性。
在C++中有二种继承:单一继承和多重继承。当一个派生类仅由一个基类派生时,称为单以继承;而当一个派生类由两个或更多个基类所派生时,称为多重继承。
类A派生类B:类A为基类,类B为派生类。
但派生类并不是简单的扩充,有可能改变基类的性质。有三种派生方式:公有派生、保护派生、私有派生。默认的是私有派生。
从一个基类派生一个类的一般格式为:
class ClassName(派生类名):<Access>(继承方式)BaseClassName(基类名)
{
private:(表示私有基类)默认
……;//私有成员说明
public:(表示公有基类)
……;//公有成员说明
protected:(表示保护基类)
……;//保护成员说明
}
-
【公有派生】
class ClassName : public BaseClassName
公有派生时,基类所有成员在派生类中保持各个成员的访问权限。
基类:public:在派生类和类外可以使用
protected:在派生类中使用
private:不能在派生类中使用
#include<iostream> using namespace std; class A { public: int z;//公有数据成员 A(int a, int b, int c) { cout << "调用类A带参构造函数" << endl; x = a; y = b; z = c; } int getx() { return x; } int gety() { return y; } int getz() { return z; } void print() { cout << "x=" << x <<",y=" << y << ",z=" << z << endl; } protected: int y;//保护数据成员 private: int x;//私有数据成员 }; class B : public A { public: B(int a, int b, int c, int d, int e):A(a, b, c){ cout << "调用派生类B带参构造函数:" << endl; m = d; n = e; } void print() { cout << "x=" << getx() <<",y=" << y << ",z=" << z << ",m=" << m << ",n=" << n << endl; } int sum() { return (getx() + y + z + m + n); } private: int m, n; }; int main() { A obj(1, 2, 3); obj.print(); B obj1(1, 2, 3, 4, 5); obj1.print(): cout << "sum=" << obj1.sum() << endl; return 0; }
-
【私有派生】
class ClassName: private BaseClassName
私有派生时,基类中公有成员和保护成员在派生类中均变为私有的,在派生类中扔可直接使用这些成员,基类中的私有成员,在派生类中不可直接使用。
基类:public:(变为私有)在派生类中使用,类外不可使用
protected:(变为私有)在派生类中使用,类外不可使用
private:不能在派生类中和类外使用
#include<iostream> using namespace std; class A { public: int z;//公有数据成员 A(int a, int b, int c) { cout << "调用类A带参构造函数" << endl; x = a; y = b; z = c; } int getx() { return x; } int gety() { return y; } int getz() { return z; } void print() { cout << "x=" << x <<",y=" << y << ",z=" << z << endl; } protected: int y;//保护数据成员 private: int x;//私有数据成员 }; class B : private A { public: B(int a, int b, int c, int d, int e):A(a, b, c){ cout << "调用派生类B带参构造函数:" << endl; m = d; n = e; } void print() { cout << "x=" << getx() <<",y=" << y << ",z=" << z << ",m=" << m << ",n=" << n << endl; } int sum() { return (getx() + y + z + m + n); } private: int m, n; }; int main() { A obj(1, 2, 3); obj.print(); B obj1(1, 2, 3, 4, 5); obj1.print(): cout << "sum=" << obj1.sum() << endl; return 0; }
-
【保护派生】
class ClassName: protected BaseClassName
保护派生时,基类中公有成员和保护成员在派生类中均变为保护的和私有的,在派生类中仍可直接使用这些成员,基类中的私有成员,在派生类中不可直接使用。
基类:public:(变为保护)在派生类中使用,类外不可使用
protected:(变为私有)在派生类中使用,类外不可使用
private:不能在派生类中和类外使用
protected成员是一种具有血缘关系内外有别的成员。它对派生类的对象而言,是公开成员,可以访问,对血缘外部而言,与私有成员一样被隐蔽。
-
【抽象类和保护的成员函数】
当定义了一个类,这个类只能用作基类来派生出新的类,而不能用这种类来定义对象时,称这种类为抽象类。当对某些特殊的对象要进行很好地封装时,需要定义抽象类
将类的构造函数或析构函数的访问权限定义为保护时,这种类为抽象类。
当把类中的构造函数或析构函数说明为私有时,所定义的类通常是没有任何实用意义的,一般情况下,不能用它来产生对象,也不能用它来产生派生类。
#include<iostream> using namespace std; class A { public: int x, y; void print() { cout << "x=" << x << ",y=" << y << endl; } protected: A(int a, int b)//基类初始化 { x = a; y = b; } }; class B: public A { public: B(int a, int b, int c):A(a, b)//可以在派生类调用A的构造函数 { m = c; } void print() { cout << "x=" << x << ",y=" << y << ",m=" << m << endl; } private: int m; //A obj;//在派生类中也不可以定义A的对象,实际上还是类外调用 } int main() { B obj(1, 2, 3); obj.print(); return 0; }
多继承
格式为:
class 类名<Access>(继承方式) 类名1,...,<Access>类名n
{
private:......;//私有成员说明;
public:......;//公有成员说明;
protected:......;//保护成员说明;
};
class D:public A, protected B, private C
{
......//派生类中新增加成员
}
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
{
cout << "调用基类A的构造函数。\n";
x = a;
}
~A()
{
cout << "调用基类A的析构函数。\n";
}
private:
int x;
};
class B
{
public:
B(int a)
{
cout << "调用基类B的构造函数。\n";
y = a;
}
~B()
{
cout << "调用基类B的析构函数。\n";
}
private:
int y;
};
class C: public A, public B
{
public:
C(int a, int b, int c):A(a),B(100)
{
cout << "调用派生类C的构造函数。\n";
z = a;
}
~C()
{
cout << "调用派生类C的析构函数。\n";
}
private:
int z;
};
int main()
{
C obj(10,50);
return 0;
}
初始化基类成员
构造函数不能被继承,派生类的构造函数必须调用基类的构造函数来初始化基类成员基类子对象。
派生类构造函数的调用顺序如下:
- 基类的构造函数
- 子对象类的构造函数
- 派生类的构造函数
当撤消派生类对象时,析构函数的调用正好相反。
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "调用基类A:默认构造函数。\n";
}
A(int a)
{
cout << "调用基类A的构造函数。\n";
x = a;
}
~A()
{
cout << "调用基类A的析构函数。\n";
}
private:
int x;
};
class B
{
public:
B(int a)
{
cout << "调用基类B的构造函数。\n";
y = a;
}
~B()
{
cout << "调用基类B的析构函数。\n";
}
private:
int y;
};
class C: public A, public B
{
public:
C(int a, int b, int c):A(a),B(100)
{
cout << "调用派生类C的构造函数。\n";
z = a;
}
~C()
{
cout << "调用派生类C的析构函数。\n";
}
private:
int z;
A obj1;
};
int main()
{
C obj(10,20);
return 0;
}
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "调用基类A:默认构造函数。\n";
}
A(int a)
{
cout << "调用基类A的构造函数。\n";
x = a;
}
~A()
{
cout << "调用基类A的析构函数。\n";
}
private:
int x;
};
class B
{
public:
B(int a)
{
cout << "调用基类B的构造函数。\n";
y = a;
}
~B()
{
cout << "调用基类B的析构函数。\n";
}
private:
int y;
};
class C: public A, public B
{
public:
C(int a, int b, int c):A(a),B(100)
{
cout << "调用派生类C的构造函数。\n";
z = a;
}
~C()
{
cout << "调用派生类C的析构函数。\n";
}
private:
int z;
A obj1, obj2;
};
int main()
{
C obj(10,20);
return 0;
}
虚基类及其它特性
其它特性
-
冲突:
#include<iostream> using namespace std; class A { public: int x; A(int a = 0) { x = a; } void print() { cout << "调用类A的print()函数" << endl; cout << "x=" << x << endl; } }; class B { public: int x; A(int a = 0) { x = a; } void print() { cout << "调用类B的print()函数" << endl; cout << "x=" << x << endl; } }; class C { public: int x; A(int a = 0) { x = a; } void print() { cout << "调用类C的print()函数" << endl; cout << "x=" << x << endl; } }; class D:public A,public B,public C { public: int d; A(int a = 0) { d = a; } public: void setx(int a) { A::x = a; } void setd() { d = b; } int getd() { return d; } }; int main() { D d1; d1.B::print(); d1.A::print(); d1.C::print(); return 0; }
-
支配原则
#include<iostream> using namespace std; class A { public: int x; void print() { cout << "调用类A的print()函数" << endl; cout << "x=" << x << endl; } }; class B { public: int y; void print() { cout << "调用类B的print()函数" << endl; cout << "y=" << y << endl; } }; class C:public A, public B { public: int y;//类B和类C均有y的数据成员 }; int main() { C c1; c1.x = 100; c1.y = 200;//未指明的情况下,给派生类中的y赋值 c1.B::y = 300;//给基类B的y赋值 c1.A::print(); c1.B::print(); cout << "y=" << c1.y << endl;//输出派生类中的y的值 cout << "y=" << c1.B::y << endl;//输出基类B中的y的值 return 0; }
-
基类与对象成员
任一基类在派生类中只能继承一次,否则,会造成成员名的冲突;
若在派生类中,确实要有两个以上基类的成员,则可用基类的两个对象作为派生类的成员。
把一个类作为派生类的基类或把一个类的对象作为一个类的成员,在使用上是有区别的:在派生类中可直接使用基类的成员(访问权限允许的话),但要使用对象成员的成员时,必须在对象名后加上成员运算符"."和成员名。
-
赋值兼容规则
虚基类
这种同一个公共的基类在派生类中产生多个拷贝,不仅多占用了存储空间,而且可能会造成多个拷贝中的数据不一致和模糊的引用。
D d;
d.x = 10;//模糊引用
在多重派生的过程中,若使公共基类在派生类中只有一个拷贝,则可将这种基类说明为虚基类。在派生类的定义中,只要在基类的类名前面加上关键字virtual,就可以将基类说明为虚基类。
class B:public virtual A{
public:
int y;
B(int a = 0, int b = 0):A(b) {y = a;}
};
#include<iostream>
using namespace std;
class A
{
public:
int x;
A(int a = 0)
{
cout << "调用类A的构造函数。" << endl;
x = a;
}
};
class B : virtual public A
{
public:
int y;
B(int a = 0, int b = 0):A(a)
{
cout << "调用类B的构造函数。" << endl;
y = b;
}
};
class C :virtual public A
{
public:
int z;
C(int a = 0, int c = 0):A(a)
{
cout << "调用类C的构造函数。" << endl;
z = c;
}
};
class D:public B, public C
{
public:
int dx;
D(int a1, int b, int c ,int d int a2):B(a1,b),C(a2, c)
{
cout << "调用类D的构造函数。" << endl;
dx = d;
}
};
int main()
{
D obj(100, 200, 300, 400, 500);
cout << "obj.x=" << obj.x << endl;
cout << "obj.y=" << obj.y << endl;
return 0;
}
虚函数与抽象类
虚函数
类中定义不同类中的同名函数的多态的行为,主要是通过虚函数来实现。
在类的成员函数前加virtual关键字。虚函数是实现包含多态的基础。这里需要说明的是当基类里有虚函数且派生类中重新声明了和基类虚函数相同的函数,那么派生类该函数也是虚函数,这个过程称为对基类虚函数进行重写,这对于实现包含多态有重要意义。
【包含多态的条件】
基类中必须包含虚函数,并且派生类中一定要对基类中的虚函数进行重写。通过基类对象的指针或者引用调用虚函数。
【静态联编和动态联编】联编是指对于相同名字的若干个函数的选择问题。
静态联编(早起联编) | 动态联编(滞后联编) | |
---|---|---|
联编时间 | 编译 | 运行 |
工程案例 | 函数重载、运算符重载 | 虚函数 |
#include<iostream>
using namespace std;
const double PI = 3.14159;
class point
{
public:
cpoint(double a, double b)
{
x = a;
y = b;
}
double area()
{
return 0;
}
private:
dourble x, y;
};
class ccircle :public cpoint //园类 继承cpoint
{
public:
ccircle(double a, double b, double c) :cpoint(a, b)
{
r = c;
}
double area()
{
reuturn PI * r * r;
}
private:
double r;
};
class cractangle :public cpoint //矩形类 继承cpoint
{
public:
cractangle(double a, double b, double c, double d) :cpoint(a, b)
{
w = c;
h = d;
}
private:
double w, h;
};
void Funcarea(cpoint &p)
{
cout << "area=" << p.area() << endl;
}
int main()
{
ccircle cobj(5.5, 6.6, 10);
Funcare(cobj);
cractangle cobjs(3, 4, 5, 6);
Funcare(cobjc);
return 0;
}
/*返回的两个结果都为零。原因:p是cpoint类的一个对象,由于没有虚函数,执行的是静态联编(在编译时就已经确定),选择的是cpiont中的area()函数。*/
/***********************************************************
*设计一个哺乳动物类Animal,由此类派生出牛类、狗类,三者都具备叫声
*CallFunc()成员函数,该函数在基类中声明为虚函数
************************************************************/
#include<iostream>
#include<string>
using namespace std;
class CAnimal
{
public:
virtual void CallFunc();//虚函数
private:
string name;
};
class CCatle :public CAnimal//派生出牛类
{
public:
virtual void CallFunc():
private:
int age;
};
class CDog :public CAnimal //派生出狗类
{
public:
virtual void CallFunc():
private:
int age;
};
void CallFunc(CAnimal *p)//通过传递不同对象实参,可以调用产生对应的成员函数,以实现多态功能
{
p->CallFunc;
}
void CAnimal::CallFunc()
{
cout << "动物此类不知道怎么叫~~~~~~~" << endl;
}
void CCattle::CallFunc()
{
cout << "牛类叫哞哞哞哞~~~~~~~" << endl;
}
void CDog::CallFunc()
{
cout << "狗类叫汪汪汪汪~~~~~~~" << endl;
}
{
CCattle cobj;
CallFunc(&cobj);
CDog cobj2;
CallFunc(&cobj2);
CAnimal cobj3;
CallFunc(&cobj3);
return 0;
}
抽象类
纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要给出各自的定义。纯虚函数的声明格式为:
virtual 函数类型 函数名(参数表) = 0;
而抽象类就是指带有纯虚函数的类。
命名空间与模板
命名空间
使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突。在C++中,变量、函数和类都是大量存在的。如果没有命名空间,这些变量、函数、类的名称都将存在于全局命名中,会导致很多冲突。
比如:如果我自己的程序中定义了一个函数strcat(),这将重写标准库中的strcat()函数,这是因为这两个函数都是位于全局命名空间中的。命名冲突还会发生在一个程序中使用两个或者更多的第三方库的情况中。此时,很有可能,其中一个库中的名称和另外一个库中的名称是相同的,这样就冲突了。这种情况会经常发生在类的名称上。比如,我们在自己的程序中定义了一个string类,而我们程序中使用的某个库中也可能定义了一个同名的类,此时名称就冲突了。
namespace关键字的出现就是针对这种问题的。由于这种机制对于声明于其中的名称都进行了本地化,就使得相同的名称可以在不同的上下文中使用,而不会引起名称的冲突。
namespace关键字使得我们可以通过创建作用范围来对全局命名空间进行分割。本质上来讲,一个命名空间就定义了一个范围。定义命名空间的基本形式如下:
namespace 名称{//声明}
在命名空间中的定义的任何东西都局限于该命名空间内。
using 指令
使用using namespace指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
#include<iostream>
using namespace std;
namespace Onespace
{
void print()
{
cout << "执行Onespace名字空间的print()函数。" << endl;
}
}
namespace Twospace
{
void print()
{
cout << "执行Twospace名字空间的print()函数。" << endl;
}
}
using namespace Onespace;
int main()
{
//1.调用第一命名空间中的print函数
Onespace::print();
Twospace::print();
//2.调用第一命名空间中的print函数
print();
return 0;
}
【嵌套的命名空间】
namespace 命名空间名称1{
//代码声明
namespace 命名空间名称2{
//代码声明
}
}
通过使用::运算符来访问嵌套的命名空间中的成员。
#include<iostream>
using namespace std;
namespace Onespace
{
void print()
{
cout << "执行Onespace名字空间的print()函数。" << endl;
}
namespace Twospace{
void print(){
cout << "执行Twospace名字空间的print()函数。" << endl;
}
}
}
using namespace Onespace::Twospace;
int main()
{
print();
return 0;
}
模板
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。模板的创建泛型类或函数的蓝图或公式。
-
函数模板
模板函数定义的一般形式如下所示:
template<typenname type类型> 返回类型 函数名 (参数列表)
{
//函数的主体;
}
type是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。
/* 求两个数字中,最大数? */ #include<iostream> #include<string> using namespace std; template <typename T> inline T const& MaxFunc(T const& a, T const& b) { return a < b ? a : b; } int main() { int i = 456, j = 683; cout << "MaxFunc(i,j):" << MaxFunc(i, j) << endl; double i = 34.45, j = 12.35; cout << "MaxFunc(i,j):" << MaxFunc(i, j) << endl; string i = "hello, world", j = "goodbye"; cout << "MaxFunc(i,j):" << MaxFunc(i, j) << endl; return 0; }
-
类模板
正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:
template <class type>class class-name{
...
}
type是占位符类型名称,可以在类被实例化的时候进行指定。使用一个逗号分隔的列表来定义多个泛型数据类型。
#include<iostream> #include<string> using namespace std; template <typename T>//模板声明,其实T为类型参数 class CompareSS { public: CompareSS(T a, T b) { x = a; y = b; } T maxfunc() { return (x > y ? x : y); } private: int x, y; }; int main() { //用类模板定义对象,此时T被int参数化代替 CompareSS<int> obj1(30,90); cout << "\n最大值为:" << obj1.maxfunc() << endl; CompareSS<double> obj2(67.34,33.87); cout << "\n最大值为:" << obj2.maxfunc() << endl; return 0; }
C++IO流类库
编译系统已经以运算符或函数的形式做好了对标准外设(键盘、屏幕、打印机、文件)的接口,使用时只需按照要求的格式调用即可。
cin >> x; cout << x; cin.get(ch);
输入输出流(I/O strea)
C++语言的I/O系统向用户提供了一个统一的接口,使得程序的设计尽量与所访问的具体设备无关,在用户与设备之间提供了一个抽象的界面:输入输出流。
【重载输入(提取)和输出(插入)运算符】
用标准流进行输入/输出时,系统自动地完成数据类型的转换。对于输入流,要将输入的字符序列形式的数据变换成计算机内部形式的数据(二进制或ASCII)后,再赋给变量,变换后的格式由变量的类型确定。对于输出流,将输出的数据变换成字符串形式后,送到输出流(文件)中。
#include<iostream>
using namespace std;
class CinCout
{
public:
CinCout(int a = 0, int b = 0)
{
c1 = a;
c2 = b;
}
void showc1c2(void)
{
cout << "c1=" << c1 << ",c2=" << c2 << endl;
}
friend istream& operator >>(istream&, CinCount&);
private:
int c1, c2;
}
istream& operator>>(istream& is, CinCount& cc)
{
is >> cc.c1 >> cc.c2;
return is;
}
int main()
{
CinCount o1, o2;
o1.showclc2();
o2.showc1c2();
cin >> o1;
cin >> o2;
o1.showclc2();
o2.showc1c2();
return 0;
}
在C++中允许用户重载运算符“<<”和“>>”,实现对象的输入和输出。重载这两个运算符时,在对象所在的类中,将重载这两个运算符的函数说明该类的友元函数。
重载输入(提取)运算符的一般格式为:
friend(友元函数) istream &(返回值类型) operater >>(函数名)(istream &(左操作数),ClassName &(右操作数));
istream & operator >> (istream &is, ClassName &f);
cin >> a; operator>>(cin, a);
返回值类型:类istream的引用,cin中可以连续使用运算符“>>”。
cin >> a >>b;
第一个参数:是“>>”的左操作数cin类型,类istream的引用。
第二个参数:是“>>”的右操作数,即欲输入的对象的引用。
#include<iostream>
using namespace std;
class InCout
{
public:
CinCout(int a = 0, int b = 0)
{
c1 = a;
c2 = b;
}
void show(void)
{
cout << "c1=" << c1 << ",c2=" << c2 << endl;
}
friend istream& operator >>(istream&, InCount&);
friend ostream& operator <<(ostream&, InCount&);
private:
int c1, c2;
}
istream& operator>>(istream& is, InCount& cc)
{
is >> cc.c1 >> cc.c2;
return is;
}
ostream& operator>>(ostream& os, InCount& cc)
{
os << "c1=" << cc.c1 << ",c2=" << cc.c2;
return os;
}
int main()
{
InCount o1, o2;
o1.showclc2();
o2.showc1c2();
cout << o1 << 02;
cin >> o1;
cin >> o2;
o1.showclc2();
o2.showc1c2();
cout << o1 << 02;
return 0;
}
重载输出(插入)运算符的一般格式为:
friend(友元函数) ostream &(返回值类型) operater >>(函数名)(ostream &(左操作数),ClassName &(右操作数));
ostream & operator<<(ostream &os, ClassName &f);
cout << a; operator<<(cout, a);
文件流
C++在头文件fstream.h中定义了C++的文件流类体系,当程序中使用文件时,要包含头文件fstream.h。
当使用文件时,在程序头有:#include<fstream.h>。其中定义了各种文件操作运算符及函数。
【程序对文本文件的操作与对键盘、显示器的操作比较:】
在涉及文本文件的操作时,将输入文件看成键盘,将输出文件看成显示器,格式不变。只需在程序中增加打开与关闭文件的语句。
【文件的操作】
- 文本文件:以ASCII表示的文件:记事本,*.cpp等
- 二进制形式表示的文件:可执行程序*.exe等
56:ASCII表示为0011010100110110,占两字节
56:二进制表示为111000,占六个二进制位
不同的文件操作的函数、格式不同
C++标准库专门提供了3个类用于实现文件操作,它们统称为文件流类,这3个类分别为:
- ifstream:专用于从文件中读取数据;
- ofstream:专用于向文件中写入数据;
- fstream:既可以用于从文件中读取数据,又可用于向文件中写入数据。
【fstream类常用成员方法】
成员方法名 | 适用类对象 | 功能 |
---|---|---|
open() | fstream、ifstream、ofstream | 打开指定文件,使其与文件流对象相关联。 |
is_open() | fstream、ifstream、ofstream | 检查指定文件是否已打开 |
close() | fstream、ifstream、ofstream | 关闭文件,切断和文件流对象的关联 |
swap() | fstream、ifstream、ofstream | 交换2个文件流对象 |
operator>> | fstream、ifstream | 重载>>运算符,用于从指定文件中读取数据 |
gcount() | fstream、ifstream | 返回上次从文件流提取出的字符个数。该函数常和get(),getline(),ignore(),peek(),read(),readsome(),putback()和unget()联用 |
get() | fstream、ifstream | 从文件流中读取一个字符,同时该字符会从输入流中消失 |
getline(str,n,ch) | fstream、ifstream | 从文件流中接受n-1个字符给str变量,当遇到指定ch字符时会停止读取,默认情况下ch为'\0' |
ignore(n,ch) | fstream、ifstream | 从文件流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出n个字符,或者当前读取的字符为ch |
peek() | fstream、ifstream | 返回文件流中的第一个字符,但并不是提取该字符 |
putback() | fstream、ifstream | 将字符c置入文件流(缓冲区) |
operator<< | fstream、ofstream | 重载<<运算符,用于向文件中写入指定数据 |
put() | fstream、ofstream | 向指定文件流中写入单个字符 |
write() | fstream、ofstream | 向指定文件中写入字符串 |
tellp() | fstream、ofstream | 用于获取当前文件输出流指针的位置 |
seekp() | fstream、ofstream | 设置输出文件输出流指针的位置 |
flush() | fstream、ofstream | 刷新文件输出流缓冲区 |
good() | fstream、ifstream、ofstream | 操作成功,没有发生任何错误 |
eof() | fstream、ifstream、ofstream | 到达输入末尾或文件尾 |
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
const char *str = "http://www.baidu.com";
//我们要创建一个fstream类的对象
fstream fs;
//将demo.txt文件和fs文件流建立关联
fs.opem("demo.txt", ios::out);
fs.write(str, 100);
fs.close();
return 0;
}
打开文件可以通过以下两种方式打开:
- 调用流对象open成员函数打开文件
- 定义文件流对象时,通过构造函数打开文件
【使用open函数打开文件】
先看第一种文件打开方式。以ifstream类为例,该类有一个open成员函数,其他两个文件流类也有同样的open成员函数:
void open(const char* szFileName, int mode);
第一个参数是指向文件名的指针,第二个参数是文件的打开模式标记。
模式标记 | 适用对象 | 作用 |
---|---|---|
ios::in | ifstream,fstream | 打开文件用于读取数据。如果文件不存在,则打开出错 |
ios::out | ofstream,fstream | 打开文件用于写入数据。如果文件不存在,在新建该文件;如果文件原来就存在,则打开时清除原来的内容 |
ios::app | ofstream,fstream | 打开文件,用于在尾部添加数据。如果文件不存在,则新建文件 |
ios::ate | ifstream | 打开一个已用的文件,并将文件读指针指向文件末尾。如果文件不存在,则打开出错 |
ios::trunc | ofstream | 打开文件时会清空内部存储的所有数据,单独使用时与ios::out相同 |
ios::binary | ifstream,fstream,ofstream | 以二进制方式打开文件,若不指定此模式,则以文本模式打开 |
ios::in|ios::out | fstream | 打开已存在的文件,既可以读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错 |
ios::in|ios::out | ofstream | 打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错 |
ios::in|ios::out|ios::trunc | fstream | 打开文件,即可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件 |
ios::binary可以和其他模式标记组合使用,例如:
- ios::in|ios::binary表示用二进制模式,以读取的方式打开文件;
- ios::out|ios::binary表示用二进制模式,以写入的方式打开文件。
在流对象上执行open成员函数,给出文件名和打开模式,就可以打开文件。判断文件打开是否成功,可以看“对象名”这个表达式的值是否为true,如果为true,则表示文件打开成功。
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
ifstream inFile;
inFile.open(".\\demo.txt",ios::in);
if(inFile)//条件成立,则说明文件打开成功
{
cout << "\ndemo.txt文件打开成功" << endl;
inFile.close();
}
else
{
cout << "\ndemo.txt文件打开失败" << endl;
return 1;
}
ofstream outFile;
outFile.open(".\\outdemo.txt",ios::out);
if(outFile)//条件成立,则说明文件打开成功
{
cout << "\noutdemo.txt文件打开成功" << endl;
outFile.close();
}
else
{
cout << "\noutdemo.txt文件打开失败" << endl;
}
fstream ioFile;
ioFile.open(".\\iodemo.txt",ios::in|ios:out|ios::trunc);
if(ioFile)//条件成立,则说明文件打开成功
{
cout << "\niodemo.txt文件打开成功" << endl;
ioFile.close();
}
else
{
cout << "\niodemo.txt文件打开失败" << endl;
}
return 0;
}
调用open()方法打开文件,是文件流对象和文件之间建立关联的过程。那么,调用close()方法关闭已打开的文件,就可以理解为是切断文件流对象和文件之间的关联。注意:close()方法的功能仅是切断文件流与文件之间的关联,该文件流并会被销毁,其后续还可用于关联其他的文件。
close()方法的用法很简单,其语法格式如下:
void close();
可以看到,该方法既不需要传递任何参数,也没有返回值。
-
【C++ ostream::write()方法写文件】
ofstream和fstream的write()成员方法实际上继承自ostream类,其功能是将内存中buffer指向的count个字节的内容写入文件,基本格式如下:
ostream & write(char *buffer, int count);
其中,buffer用于指定要写入文件的二进制数据的起始位置;count用于指定写入字节的个数。也就是说,该方法可以被ostream类的cout对象调用,常用于向屏幕上输出字符串。同时,它还可以被ofstream或者fstream对象调用,用于将指定个数的二进制数据写入文件。
同时,该方法会返回一个作用于该函数的引用形式的对象。举个例子,obj.write()方法的返回值就是对obj对象的引用。
#include<iostream> #include<fstream> using namespace std; class student { public: int no; char name[10]; int age; }; int main() { student stu; ofstream outFile("student.dat",ios::out|ios::binary); if(!outFile) { cout << "打开文件失败" << endl; outFile.close(); } while(cin >> stu.no >> stu.name >> stu.age) { outFile.write((char*)&stu,sizeof(stu)); } outFile.close(); return 0; }
-
【C++ istream::read()方法读文件】
ifstream和fsteam的read()方法实际上继承自istream类,其功能正好和write()方法相反,即从文件中读取count个字节的数据。该方法的语法格式如下:
istream & read(char* buffer,int count);
其中buffer用于指定读取字节的起始位置,count指定读取字节的个数。同样,该方法也会返回一个调用该方法的对象的引用。
#include<iostream> #include<fstream> using namespace std; class student { public: int no; char name[10]; int age; }; int main() { student stu; ifstream inFile("student.dat",ios::in|ios::binary);//以二进制读方式打开此文件 if(!inFile) { cout << "打开文件失败" << endl; inFile.close(); } while(inFile.read((char*)&stu,sizrof(stu))) { cout << stu.no << "," << stu.name << "," << stu.age << endl; } inFile.close(); return 0; }
-
【C++ ostream::put()成员方法】
掌握了如何通过执行cout.put()方法向屏幕输出单个字符。fstream和ofstream类继承自ostream类,因此fstream和ostream类对象都可以调用put()方法。
当fstream和ofstream文件流对象调用put()方法时,该方法的功能就变成了向指定文件中写入单个字符。put()方法的语法格式如下:
ostream & put(char c);
其中,c用于指定要写入文件的字符。该方法会返回一个调用该方法的对象的引用形式。
#include<iostram> #include<fstream> using namespace std; int main() { char ch; ofstream outFile("outdemo.txt", ios::out|ios::binary); if(!outFile) { cout << "\noutdemo.txt文件打开失败" << endl; return 0; } else cout << "\noutdemo.txt文件打开成功" << endl; while(cin >> ch) { outFile.put(c); } outFile.close(); return 0; }
-
【C++ istream::get()成员方法】
与put()成员方法的功能相对的是get()方法,其定义在istream类中,借助cin.get()可以读取用户输入的字符。在此基础上,fstream和ifstream类继承自istream类,因此fstream和ifstream类的对象也能调用get()方法。
当fstream和ifstream文件流对象调用get()方法时,其功能就变成了从指定文件中读取单个字符(还可以读取指定长度的字符串)。这里仅介绍最常用的2种:
int get();
istream & get(char & c);
其中,第一种语法格式的返回值就是读取到的字符,只不过返回的是它的ASCII码,如果碰到输入的末尾,则返回值为EOF。第二种语法格式需要传递一个字符变量,get()方法会自行将读取到的字符赋值给这个变量。
#include<iostream> #include<fstream> using namespace std; int main() { char ch; ifstream inFile("outdemo.txt", ios::out|ios::binary); if(!inFile) { cout << "\noutdemo.txt文件打开失败" << endl; return 0; } else cout << "\noutdemo.txt文件打开成功" << endl; while((ch = inFile.get()) && ch != EOF) { cout << ch; } inFile.close(); return 0; }
-
【文件指针】
当一打开文件,文件指针位于文件头,并随着读写字节数的多少顺序移动。可以利用成员函数随机移动文件指针。
【随机读取二进制文件】
infile.seekg(int);//将文件指针移动到由参数指定的字节处
infile.seekg(100);//将文件指针移动到距离文件头100字节处
infile.seekg(int(移动的字节数), ios::_dir(相对位置));
_dir:beg:文件头 cur:当前位置 end:文件尾
infile.seekg(100,ios::beg);//移动到距文件头100个字节
infile.seekg(-100,ios::cur);//移动到距当前位置前100个字节
infile.seekg(-500,ios::end);//移动到距文件尾前500个字节
STL(vector_deque_stack)
STL是Standard Template Libary的简称,中文名标准模板库,惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。这里的“容器”和算法的集合指的是世界上很多聪明人很多年的杰作。STL的目的是标准化组件,这样就不用重新开发,可以使用现成的组件。STL是C++的一部分,因此不用安装额外的库文件。
STL的版本很多,常见的有HP STL、PJ STL、SGI STL等
在C++标准中,STL被组织为下面的13个头文件:<algorithm>,<deque>,<functional>,<iterator>,<vector>,<list>,<map>,<memory.h>,<numeric>,<queue>,<set>,<stack>和<utility>。
STL(vector) http://www.cplusplus.com/reference/vector/vector/
-
序列式容器vector:向量(vector)连续存储的元素<vector>
vector容器是STL中最常用的容器之一,它和array容器非常类似,都可以看做是对C++普通数组的“升级版”。不同之处在于,array实现的是静态数组(容器固定的数组),而vector实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector会动态调整所占用的空间,整个过程无需人工干预。
vector常被称为向量容器,因为该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要消耗时间),时间复杂度为线性阶O(n)。
#include<iostream> #include<vector> using namespace std; int main() { //创建存储的double数据类型的一个vector容器——>v1 vector<double> v1; //创建容器v2且同时给它进行初始化元素的个数 vector<int> v2{24,45,23,57,25,85,96,56,29,99}; //创建vector容器,指定元素的个数为50 vector<int> v3(50); //创建vector容器,它拥有10个字符为'A' vector<char> v4(10,'A'); //将v4赋给v5 vector<char> v5(v4); int a[] = {10,20,30}; vector<int> v6(a, a + 2);//v6将会保存10, 20 vector<int> v7{1,2,3,4,5,6,7,8,9,10}; vector<int> v8(begin(v7),begin(v7) + 3);//v8将会保存为:1,2,3 return 0; }
#include<iostream> #include<vector> using namespace std; int main() { //定义一个空的vector容器 vector<char> vi; //向容器添加S T L T E M P L A T E vi.push_back('S'); vi.push_back('T'); vi.push_back('L'); vi.push_back('T'); vi.push_back('E'); vi.push_back('M'); vi.push_back('P'); vi.push_back('L'); vi.push_back('A'); vi.push_back('T'); vi.push_back('E'); //输出容器vi元素的个数 size() cout<< "元素个数为:" << vi.size() << endl; //遍历容器 cout << "\n输出vi容器数据元素:\n"; for(auto int i = vi.begin(); i < vi.end(); i++) cout << " " << *i << ""; cout << endl; //----------------------------------------------- //插入数据元素到头部 vi.insert(vi.begin(),'V'); cout << "\n输出vi容器数据元素:\n"; for(auto int i = vi.begin(); i < vi.end(); i++) cout << " " << *i << ""; cout << endl; //----------------------------------------------- //插入数据元素到尾部 vi.insert(vi.end(),'I'); cout << "\n输出vi容器数据元素:\n"; for(auto int i = vi.begin(); i < vi.end(); i++) cout << " " << *i << ""; cout << endl; cout<<"输出vi容器首个元素为:" << vi.at(0) << endl; return 0; }
STL(deque)https://en.cppreference.com/w/cpp/container/deque
-
deque是double-ended queue的缩写,又称双端队列容器。
前面已经接触过vector容器,值得一提的是,deque容器和vector容器有很多相似之处,比如:deque容器也擅长在序列尾部添加或删除元素(时间复杂度为o(1)),而不擅长在序列中间添加或删除元素。deque容器也可以根据需要修改自身的容量和大小。
和vector不同的是,deque还擅长在序列头部添加或删除元素,所耗费的时间复杂度也为常数阶o(1)。并且更重要的一点是,deque容器中存储元素并不能保证所有元素都存储到连续的内存空间中。
当需要向序列两端频繁的添加或删除元素时,应首选deque容器。
#include<iostream> #include<deque> using namespace std; int main() { //创建deque容器,没有任何数据元素 deque<int> d1; //创建deque容器且有50个元素 deque<int> d2(50); //创建一个具有9个元素的deque容器,并且进行初始化值 deque<int> d3(9,888); //容器之间可以赋值 deque<int> d4(10); deque<int> d5(d4)>; return 0; }
#include<iostream> #include<deque> using namespace std; int main() { //定义空的容器 deque<int> d1; //向容器的尾部插入数字 d1.push_back(10); d1.push_back(20); d1.push_back(30); d1.push_back(40); d1.push_back(50); d1.push_back(60); d1.push_back(70); d1.push_back(80); d1.push_back(90); d1.push_back(100); //输出d1元素的个数 cout << "输出d1容器元素的个数为:" << d1.size() << endl; //向d1容器头部添加数据元素值 d1.push_back(888); //输出容器所有元素值 cout << "输出d1容器所有元素的值:" for(auto i=d1.begin(); i < d1.end(); i++) cout << " " << *i << " "; cout << endl; //删除容器头部的数据元素值 d1.pop_front(): cout << "输出d1容器所有元素的值:" for(auto i=d1.begin(); i < d1.end(); i++) cout << " " << *i << " "; cout << endl; return 0; }
STL(stack)http://www.cplusplus.com/reference/stack/stack/
-
容器适配器是一个封装了序列容器的类模板,它在一般序列容器的基础上提供了一些不同的功能。之所以称作适配器类,是因为它可以通过适配容器现有的接口来提供不同的功能。
stack<T>容器适配器中的数据是以LIFO的方式组织的,这和自助餐厅中堆叠的盘子、箱子中的一堆书类似。理论上的stack容器及其一些基本操作。只能访问stack顶部的元素;只有在移除stack顶部的元素后,才能访问下方的元素。
#include<iostream>
#include<stack>
#include<list>
using namesapce std;
int main()
{
//创建一个stack容器适配器
list<int> LS{11,22,33,444,55,66,77,88,99};
stack<int,list<int>> mystack(LS);
//查询mystack存储元素的个数
cout << "栈容器数据元素的个数为:" << mystack.size() << endl;
//输出栈容器数据元素的值:
cout << "输出栈容器数据元素的值:" << endl;
while(!mystack.empty())//没有元素返回真true,否则返回false
{
cout << "mystack.top()" << " ";
//将栈顶元素弹出去
mystack.pop();
}
cout << endl;
return 0;
}
STL(queue_set_map)
STL(queue) http://www.cplusplus.com/reference/queue/queue/
-
【queue(double-ended queue,双端队列)】
队列也是一种逻辑数据结构,其具有先进先出的特性,只能在队的前端进行删除,在队的后端进行插入,针对这种特性,可以实现一些较为复杂的逻辑。在实际应用中,部分程序也正需要这样一种顺序进出的数据处理方式。使用这样的逻辑处理方式,使得我们可以将更多精力放在如何处理顺序逻辑之外的事情,对于编程、开发来讲,提供了极大的方便。
只能访问queue<T>容器适配器的第一个和最后一个元素。只能在容器的末尾添加新元素,只能从头部移除元素。许多程序都使用了queue容器。queue容器可以用来表示商场结账对列或服务器上等待执行的数据库事务对列。对于任何需要用FIFO准则处理的序列来说,使用queue容器适配器都是好的选择。
#include<iostream>
#include<queue>
#include<list>
using namespace std;
int main()
{
//创建一个使用list容器作为基础容器空的vq容器适配器
queue<int,list<int>>vq;
deque<int> d1 {1, 2, 3, 4, 5};
queue<int> vq1(d1);
queue<int> vq2(vq1);
queue<int> vq3 = vq2;//等价 queue<int> vq2(vq1)
//构建一个queue容器适配器
deque<int> d1 {11,22,33,44,55};
queue<int> qd(d1);
//求出qd存储元素的个数
cout << "\n输出qd容器元素个数为:" << qd.size() << endl;
//输出qd容器所有元素的值
while(!qd.empty())
{
cout << qd.front() << endl;
//访问过的元素出对列
qd.pop();
}
//求出qd存储元素的个数
cout << "\n输出qd容器元素个数为:" << qd.size() << endl;
return 0;
}
STL(set) http://www.cplusplus.com/reference/set/set/
STL对这个序列可以进行查找、插入、删除序列中的任意一个元素,而完成这些操作的时间同这个序列中元素个数的对数成比例关系,并且当游标指向一个已删除的元素时,删除操作无效。而一个经过更正的和更加实际的定义应该是:一个集合(set)是一个容器,它其中所包含的元素的值是唯一的。这在收集一个数据的具体值的时候有用的。集合中的元素按一定的顺序排列,并被称为集合中的实例。一个集合通过一个链表来组织,在插入操作和删除操作上比向量(vector)快,但查找或添加末尾的元素时会有些慢。具体实现采用了红黑树的平衡二叉树的数据结构。
#include<iostream>
#include<set>
#include<string>
using namespace std;
int main()
{
//创建一个set容器;为空的
set<string> mys;
cout << "测试容器mys长度:" << mys.size() << endl;
mys.insert("www.baidu.com");
mys.insert("www.163.com");
mys.insert("www.126.com");
mys.insert("www.0vice.com");
cout << "测试容器mys长度:" << mys.size() << endl;
cout << "\n输出mys容器所有元素:" << endl;
for(auto it = mys.begin(); it != mys.end; it++)
{
cout << *it << endl;
}
return 0;
}
STL(map) http://www.cplusplus.com/reference/map/map/
映射和多重映射基于某一类型(key)的键集的存在,提供对T类型的数据进行快速和高效的检索。对map而言,键只是指存储在容器中的某一个成员。Map不支持副本键,multimap支持副本键。Map和multimap对象包涵了键和各个键有关的值,键和值的数据类型是步相同的,这与set不同。set中的key和value是key类型的,而map中的key和value是一个pair结构中的两个分量。
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
//创建一个空的mm容器
map<string,string,greater<string>> mm;
//直接向mm容器指定位置构造新键值对
mm.emplace("C语言程序设计","http://www.baidu,com");
mm.emplace("C++语言程序设计","http://www.google,com");
cout << "输出mm容器存储键值对的个数为:" << mm.size() << endl;
if(!mm.empty())
{
for(auto iter = mm.begin(); iter != mm.end(); iter++)
{
cout << iter->first << "\t" << iter->second << endl;
}
}
return 0;
}
/*CLASS TEMPLATE map
temple<
class _Kty,//指定键(key)的类型
class _Ty,//指定值(value)的类型
class _Pr = less<_Kty>,//指定排序规则
class _Alloc = allocator<pair<const _Kty, _Ty>>//指定分配器对象类型
>class map;
C++异常处理
程序中常见的错误有两大类:语法错误和运行错误。在编译时,编译系统能发现程序中的语法错误。
在设计程序时,应当事先分析程序运行时可能出现的各种意外的情况,制定出相应的处理措施,这就是程序的异常处理任务。
在运行没有异常处理的程序时,如果运行情况出现异常,由于程序本身不能处理,程序只能终止程序。如果在程序中设置了异常处理机制,则在运行情况出现异常时,由于程序本身已规定了处理的方法,于是程序的流程就转到异常处理代码段处理。
异常(exception)是运行时(run-time)的错误,通常是非正常条件引起的,例如:下标(index)越界,new操作不能正常分配所需内存。C语言中,异常通常是通过被调用函数返回一个数值作为标记的。
C++中,函数可以识别标记为异常的条件,然后通告发生了异常。这种通告异常的机制称为抛出异常(throwing an exception)。
异常提供了一种转移程序控制权的方式。C++异常处理涉及到了三个关键字:try、catch、throw。
- throw:当问题出现时,程序会抛出一个异常。这是通过使用throw关键字来完成的。
- catch:在你想要处理问题的地方,通过异常处理程序捕获异常。catch关键字用来捕获异常。
- try:try块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个catch块。如果有一个块抛出一个异常,捕获异常的方法会使用try和catch关键字。try块中放置可能抛出异常的代码,try块中的代码被称为保护代码。
/*C++语言通过throw语句和try...catch语句实现对异常处理。
throw语句格式如下:
throw 表达式;
其实此语句抛出一个异常,异常是一个表达式,其值的类型可以是基本类型,也可以是类。
try...catch语句的语法格式如下:
try{语句组}
catch(异常类型)
{异常处理代码}
……
catch(异常类型)
{异常处理代码}
*/
#include<iostream>
using namespace std;
int main()
{
int x, y;
cout << "请输入x,y的值:";
cin >> x >> y;
try{
if(y == 0)
throw -1;//抛出-1类型异常
else if(x == 0)
throw -1.0;//抛出-1.0类型异常
else
cout << "x/y=" << x/y << endl;
}
catch(int e)
{
cout << "catch(int):" << e << endl;
}
catch(double d)
{
cout << "catch(double):" << d << endl;
}
return 0;
}
C++标准异常类exception:
bad_typeid
bad_case
bad_alloc
ios_base::failure
logic_erroe——>out_of_range
标签:文件,专题,函数,int,派生类,高级,C++,基类,include From: https://www.cnblogs.com/sha-ckle/p/17245366.html