文章目录
前言
计算机语言的发展
计算机的工作是用程序来控制的,程序是指令的集合,指令是计算机可以识别的命令。
1.机器语言
由计算机硬件系统可以识别的二进制指令组成的语言称为机器语言。
2.汇编语言
将机器指令映射为一些可以被人读懂的助记符,如ADD、SUB等。
3.高级语言
高级语言屏蔽了机器的细节,提高了语言的抽象层次;程序中可以采用具有一定含义的数据命名和容易理解的执行语句。这使得在书写程序时可以联想到程序所描述的具体事物.
程序设计方法的发展
1.面向过程的程序设计方法
设计思路:自顶向下,逐步求解。每一模块均是由顺序、选择、循环构成
缺点:可重用性差、数据安全性差、难以开发大型软件和图形界面的应用软件
2.面向对象的程序设计方法
将数据及对数据的操作方法封装在一起,作为一个相互依存、不可分离的整体——对象。
对同类型对象抽象出其共性,形成类。
类通过一个简单的外部接口,与外界发生关系。
对象与对象之间通过消息进行通信。
优点:更直接地描述客观世界中存在的事物(对象)以及它们之间的关系。
通过类的继承与多态实现代码重用。
满足了人们对信息的需求量越来越大,对软件开发的规模也越来越大,对软件可靠性和代码的重用性的要求越来越高的客观需要。
一、面向对象的四大特点
抽象性
对象:是系统中具有属性(数据项)和行为(操作代码)的用来描述客观事物的一个实体。
类:是具有相同属性(数据项)和行为(操作代码)的一组对象的集合,为属于该类的全部对象提供了抽象的描述,包括属性和行为两个部分。
类和对象的关系:属于某个类的对象称为该类的一个实例。
封装性
封装是指把对象的属性和行为结合成一个独立的单位,称为封装体。
封装体具有独立性(各对象之间相对独立,互不干扰)和隐藏性(将对象中某些部分对外隐蔽,只留下少量接口与外界联系)。
继承性
一个特殊类的对象拥有其一般类的全部属性和行为,称作特殊类对一般类的继承。例如:轮船是一个一般类,客轮是一个特殊类。
利用继承的方法可以很方便利用一个已有的类建立一个心的类。这就是“软件重用”的思想。
多态性
指一种行为多种实现。
例如函数重载和运算符重载(同一个类中同一种行为)以及虚函数(同一种行为在一般类和特殊类中有不同实现)。
二、类和对象
1.类的定义
//说明部分
class <类名>{
public:
<成员函数或数据成员的说明或实现>;
private:
<数据成员或成员函数的说明或实现>;
};
//实现部分
<函数类型> <类名>:: <成员函数名>(<参数表>){
<函数体>
}
例:定义一个圆类
//定义圆类
class Circle{
public:
Circle(int nr){radius=nr;)//构造函数
void setR(int new_r);//成员函数
private:
int radius;//数据成员
};
void Circle::setR(int new_r){
radius=new_r;
}
注意事项:类体中不允许对数据成员初始化
class Circle{
public:
void setR(int new_r);
private:
int radius(10);//错误
};
类的说明部分放到*.h文件,实现部分放在*.cpp文件
2.对象的定义
<类名> <对象名表>;
例:
Clock myClock,*pC, clocks[30];//对象、对象指针、对象数组
Clock &cl = myClock;//定义一个对象的引用
对象成员的表示:
//一般对象:
//对象数组元素的成员表示同一般对象
<对象名>.<数据成员名>;
<对象名>.<成员函数名>(<参数表>);
例:
Circle a;
int b=a.radius;//错,radius是私有成员,公有成员时才可用
a.setR(10);
Circle arr[10];
arr[0].setR(15);
Circle C[2]={Circle(1),Circlr(2)};
//指向对象的指针的成员
<对象指针名>-><数据成员名>
<对象指针名>-><成员函数名>(<参数表>)
例:
Clock *b = &a;
b->radius;//错,radius是私有成员,公有成员时才可用
b->setR(10);
//对象引用的成员
<对象引用名>.<数据成员名>
<对象引用名>.<成员函数名>(<参数表>)
例:
Clock &cl = myClock;
cl.Hour
cl.SetTime(10,12,14);
常对象
const修饰对象。常对象的数据成员都是常数据成员,不能改变。常对象只能调用常成员函数。
常指针:
地址为常量的指针
int *const p1=&a;
*p1=1;
pq=&c//非法
值为常量的指针
const int *p2=&a;
*p2=1;//非法
p2=&c;
常引用:
不可以通过引用修改参数的值
const int &c=a;
c=1;//非法
a=1;
子对象
在一个类中可以使用另一个类的对象作为其数据或成员。
class B{
public:
B(int i,int j){
b1=i,b2=j;
}
private:
int b1,b2;
};
class A{
public:
A(int i,int j,int k):b(i,j){//子对象初始化如有多个对象,初始化时中间加逗号
a=k;
}
private:
B b;
int a;
};
堆对象
也叫动态对象
使用new创建对象,使用delete释放对象
A *pa;
pa=new A(3);//创建一个对象
delete pa;
int *p;
p=new int(8);//创建int类型的变量
delete p;
A *parr;
parr=new A[10];//创建一个对象数组
parr[0]=A(10);//使用默认构造函数进行初始化
delete []parr;
3.类的成员
数据成员
普通数据成员
属于一个类的具体的对象
常量数据成员
值不能改变。初始化通过成员初始列表实现。
class A{
public:
A(int i,int j):a(i){b=j;}//常数据成员初始化
private:
const int a;
int b;
};
静态数据成员
静态成员解决了数据共享的问题,静态成员的特点是它不是属于某个对象的,而是属于某个类的。静态成员的访问可以通过对象引用,也可以通过类名引用。
class A{
int a,b,c;
static int sum;
public:
A(int i=1,int j=2,int k=3){a=i,b=j,c=k;}
int f(){sum+=(a+b+c);return sum;//return A::sum;}
};
int A::sum=0;//静态数据成员初始化
//A(int i,int j):a(i){b=j;}//常数据成员初始化
int main(){
A a;
cout<<a.f()<<endl;
}
成员函数
(1)普通成员函数:
构造函数和析构函数:
构造函数的功能是在创建对象时,用给定的值对对象进行初始化。
构造函数的名字同类名,无返回值,创建对象时系统自动调用构造函数。
析构函数的功能是用来释放一个对象。
析构函数名同类名,在前面加“~”符号,无返回值,无参数(不能被重载),系统自动调用。
析构函数在下边3种情况时被调用:
1、对象生命周期结束,被销毁时
2、delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类析构函数是虚函数时;
3、对象A是对象B的成员,B的析构函数被调用时,对象A的析构函数也被调用。
//构造函数和析构函数的实现与调用
class Circle{
private:
int radius;
public:
Circle(int new_r);
~Circle();
};
Circle::Circle(int new_r){
radius=new_r;
cout<<"Constructor called.\n";
}
Circle::~Circle(){
cout<<"Desturctor called.\n";
}
int main(){
Circle c(0,0,0);//隐含调用构造函数
}
运行结果:
Constructor called.
Desturctor called.
默认构造函数和默认析构函数:
是不带参数的,或自带初始值。程序员没有自定义时,系统会自动提供一个,无参数无代码。
数组元素所属类的构造函数:无构造函数时,采用默认构造函数。可以声明具有形参值的构造函数。数组中每一个对象被删除时,系统都要调用一析构函数。
//默认构造函数与默认析构函数
class Clock{
private:
int Hour;
int Minute;
int Second;
public:
Clock(){}//默认构造函数
或 Clock(int a=0,int b=0,int c=0){
Hour=a,Minute=b,Second=c;
}//默认构造函数
};
int main(){
Clock c;//使用了系统自动提供的默认构造函数
}
拷贝构造函数:
用已知对象初始化创建另一对象时所用的构造函数。如果没有定义拷贝构造函数,系统自动创建一个默认。
Circle(Circle& c){
radius=c.radius;
}//拷贝构造函数
int main(){
Circle A(1);
Circle B(A);//拷贝构造函数调用
}
若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。
void fun(Circle ci){}
int main(){
Circle A(10);
fun(A);//调用拷贝构造函数
}
系统返回值是类对象时,系统自动调用拷贝构造函数。
Circle fun2(){
Circle B(9);
return B;//调用拷贝构造函数
}
int main(){
Circle A;
A=fun2();
}
赋值运算符函数:
class Circle{
private:
int radius;
public:
Circle(){
radius = 0;
}
Circle(int r){
radius=r;
}
void operator=(const Circle &c){
radius=c.radius;
}
};
Circle c1,c2(2);
c1=c2;
在使用重载赋值运算符给对象赋值时,如果右表达式不是一个对象,先转换成一个对象再赋值。构造函数具有类型转换功能。
D d;
d=20;//调用构造函数
类型转换函数:
可以将类类型转换为其它类型
#include <iostream>
using namespace std;
class E{
public:
E(int i,int j){ den=i; num=j; }//构造函数
operator double();//类型转换函数
private:
double den, num;
};
E::operator double(){
return double(den)/double(num);
}
int main(){
E e(6,10);//构造一个对象e
double a(3.5);//a为double类型
a += e-2;//通过类的类型转换函数将e转换为double类型
cout<<a<<endl;
}
(2)常成员函数:
常成员函数可以引用const数据成员,也可引用非const的数据成员。非const数据成员在常成员函数中可以引用,但不可改变(在运算符重载中用到)。
void print()const{}
还有其它程序员自定义函数
(3)静态成员函数:
例:
/*静态成员和静态成员函数*/
//静态成员通过整个类都可以访问,可以使用类名也可以使用变量名
//静态成员必须初始化
//在静态成员函数中可以直接引用其静态成员,而引用非静态成员时需用对象名引用
#include<iostream>
using namespace std;
//静态成员属于整个类,可以通过类名和对象名访问
class Point
{
private:
int X,Y;
static int countP;//静态数据成员
public:
Point(int xx=0,int yy=0) { X=xx; Y=yy; countP++; }
Point(Point &p);//拷贝构造函数
int GetX( ) { return X; }
int GetY( ) { return Y; }
static void GetC( ) { cout<<" Object id="<<countP<<endl; }//静态成员函数
//在静态成员函数中可以直接引用其静态成员,而引用非静态成员时需用对象名引用
};
Point::Point(Point &p){
X=p.X;
Y=p.Y;
countP++;//每一次构造,countP值加1
}
int Point::countP=0;//在类外初始化countP=0,静态数据成员必须初始化
int main( )
{
Point A(4,5);//声明Point对象A,使用构造函数1次
cout<<"Point A,"<<A.GetX()<<","<<A.GetY();
A.GetC();//通过对象名A使用静态成员函数,与使用类名效果相同
Point B(A);//拷贝A构造B,使用构造函数1次
cout<<"Point B,"<<B.GetX()<<","<<B.GetY();
Point::GetC();//通过类名使用静态成员函数
}
(4)成员函数的特性
内联函数
1.在内联函数内不允许使用循环语句和开关语句,一般小于10句;
2.内联函数的定义必须出现在内联函数第一次调用之前;
3.类结构中所在的类说明内部定义的函数是内联函数。
class A{
public:
void f(){cout<<"f()";}//类内定义自动为内联函数
//尽管没有inline修饰符,在类内进行定义的成员函数也是内联函数。
};
或
class A{
public:
void f();
};
inline void f(){cout<<"f()";}
//inline声明应该修饰在函数的定义,而不是函数的声明。
int main()
{
A a;
a.f();
return 0;
}
重载性
函数名相同却实现不同的功能。
成员函数可以重载,可以依靠参数的个数、参数的类型来区别。
不能通过返回值类型区别。
设置参数的默认值
成员函数的参数可以设置为默认值。
可以给一个或多个参数设置默认值;
指定了默认值的参数的右边,不能出现没有指定默认值的参数。
A(int i=8, int j=10, int k);
//错,会影响重载
正确:
A(int i=8, int j=10, int k=12);
A X,Y(5),Z(7,9,11);
结果:
8,10,12
5,10,12
7,9,11
4.指向类的成员的指针
通过指向成员的指针只能访问公有成员
指向公有数据的指针、指向公有成员函数的指针、指向数组的指针和指针数组
class A{
public:
int fun(int b){return c+b;}
int c;
};
//指向公有数据成员的指针
//说明指针指向哪个成员
int A::*pc=&A::c;
//通过对象名(或对象指针)与成员指针结合来访问数据成员
A a; a.*pc=8;
A *p; p->*pc=8;
//指向公有成员函数的指针
int (A::*pfun)(int)=A::fun;//初始化
//通过对象名(或对象指针)与成员指针结合来访问函数成员
A a; a.*pfun(9);
A *p; p->*pfun(9);
//指向数组的指针
A aa[2][3];
A (*paa)[3]=aa;//指向一维数组的指针
(*(*(paa+i)+j)).print();
//指针数组
A *arr[3]={&a1,&a2,&a3};
arr[0]=&a1;
arr[1]->print();
this指针
隐含于每一个类的成员函数中的特殊指针。
当通过一个对象调用成员函数时,系统先将该对象的地址赋给this指针,然后调用成员函数,成员函数对对象的数据成员进行操作时,就隐含使用了this指针。
this->radius=new_r;//使用this指针引用类的成员
void A::Copy(A &a){//拷贝函数
if(this==&a)
return;
*this=a;
}
5.友元
友元是C++提供的一种破坏数据封装和数据隐藏的机制。
通过将一个模块声明为另一个模块的友元,一个模块能够引用到另一个模块中本是被隐藏的信息。
可以使用友元函数和友元类。
为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元。
友元函数
友元函数前边加 friend 关键字,说明在类体内。如被定义在类体外,不加类名限定。
友元函数可以访问类中的私有成员和其他成员。
友元函数的作用在于可以提高程序的运行效率。
友元函数在调用上同一般函数。
public:
friend double Distance( Point a, Point b );
double Distance ( Point a, Point b ) { // 类体外定义,无需类名
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt( dx*dx + dy*dy );
}
友元类
若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。
友元关系是不可逆的:
B类是A类的友元类,不等于A类是B类的友元类。
友元关系是不可传递的:
B类是A类的友元类,C类是B类的友元类,C类不一定就 是A类的友元类。
class X{
friend class Y;
private:
int x;
public:
X(int i){x=i;}
};
class Y{
public:
void print(X &r){
cout<<r.x;
}
};
int main(){
X m(2);
Y n;
n.print(m);
}
三、运算符重载
在C++中,一个类类型定义运算符函数的名字是关键字operator后紧跟要重载的运算符。操作数是函数的参数,运算结果是返回值。
1.常用运算符重载
函数的参数个数取决于两个因素:
按照运算符的操作个数可分为一元运算符(如“=”)和二元运算符(如“+”、“-”)。
按照运算符函数可分为成员函数和全局函数。
运算符返回值类型:
1.如果仅仅只是读参数的值,而不改变参数,应该作为const引用来传递。当运算符函数是类的成员函数时,就将其定义为const成员函数。
2.如果使用运算符的结果是产生一个新值,就需要产生一个作为返回值的新对象,通过传值方式返回。如果函数返回的是原有对象,则通常以引用方式返回,根据是否希望对返回的值进行运算来决定是否返回const引用。
3.所有赋值运算符均改变左值。一般赋值运算符的返回值是非const引用,以便能够对刚刚赋值的对象进行运算。
4.逻辑运算符和关系运算符最好返回bool值,也可返回int值或者由typedef定义的等价类型。
定义为成员函数时:
调用函数的对象(this所指)作为第一个操作数,一元运算符无需参数,二元运算符将当前对象(this指向的对象)作为左操作数,需要提供一个参数作为右操作数。
例:重载一个Byte类
#include <bits/stdc++.h>
using namespace std;
class Byte {
unsigned char b;//定义无符号字符
public:
Byte(unsigned char bb = 0) : b(bb) {}//默认构造函数
//重载运算符+,-,*,/,%
//返回值const只读变量不允许重新赋值,const常引用-可以引用的同时不改变参数的值 const常成员函数-不改变参数的值
//return构造临时的对象 左操作数b右操作数right.b 也可以在函数体内构造后最后返回,临时对象效率高
const Byte operator+(const Byte& right) const { return Byte(b + right.b);}
const Byte operator-(const Byte& right) const { return Byte(b - right.b);}
const Byte operator*(const Byte& right) const { return Byte(b * right.b);}
const Byte operator/(const Byte& right) const {
assert(right.b != 0);//检查是否成立
return Byte(b / right.b);
}
const Byte operator%(const Byte& right) const {
assert(right.b != 0);
return Byte(b % right.b);
}
//重载位运算符 ^,&,|,<<,>>
const Byte operator^(const Byte& right) const { return Byte(b ^ right.b);}
const Byte operator&(const Byte& right) const { return Byte(b & right.b);}
const Byte operator|(const Byte& right) const { return Byte(b | right.b);}
const Byte operator<<(const Byte& right) const { return Byte(b << right.b);}
const Byte operator>>(const Byte& right) const { return Byte(b >> right.b);}
Byte& operator=(const Byte& right) {
if(this == &right) return *this;
b = right.b;
return *this;
}
//重载复合赋值运算符+=,-=,*=,/=,%=,^=,&=,|=,<<=,>>=,返回值为对象的引用
Byte& operator+=(const Byte& right) {
b += right.b;
return *this;
}
Byte& operator/=(const Byte& right) {
assert(right.b != 0);
b /= right.b;
return *this;
}
Byte& operator^=(const Byte& right) {
b ^= right.b;
return *this;
}
//重载关系运算符==,!=,<,&&,||
bool operator==(const Byte& right) const { return b == right.b;}
bool operator!=(const Byte& right) const { return b != right.b;}
bool operator<(const Byte& right) const { return b < right.b;}
bool operator&&(const Byte& right) const { return b && right.b;}
bool operator||(const Byte& right) const { return b || right.b;}
unsigned char get_b() { return b;}
};
int main()
{
//测试运算符
unsigned char a,b;
cout<<"please enter two character:";
cin>>a>>b;
Byte b1(b),b2(a),b3;
b3=b1+b2;
cout<<"a+b="<<b3.get_b();
b3=b1-b2;
cout<<"\na-b="<<b3.get_b();
b3=b1&b2;
cout<<"\na&b="<<b3.get_b();
b3=b1<<b2;
cout<<"\na<<b="<<b3.get_b();
b3=b1/b2;
cout<<"\na"<<"\\"<<"b="<<b3.get_b();
b3=b1%b2;
cout<<"\na%b="<<b3.get_b();
b1+=b2;
cout<<"\na+=b;a="<<b1.get_b();
if(b1==b2) cout<<"\nb1==b2";
else if(b1!=b2) cout<<"\nb1!=b2";
if(b1&&b2) cout<<"\nb1&&b2 is true";
return 0;
}
定义为全局函数时:
通常声明为类的友元函数,重载一元运算符需要提供一个类类型的参数,重载二元运算符需要提供两个参数,其中至少一个参数是类类型。
使用成员运算符的限制是左操作数必须是当前类的对象,左操作数不能进行自动类型转换,而全局运算符为两个操作数都提供了转换的可能性。因此,如果左操作数是其他类的对象,或是希望运算符的两个操作数都能进行类型转换,则使用全局函数重载运算符。
#include<bits/stdc++.h>
using namespace std;
class Number {
int i;
public:
Number(int ii = 0):i(ii) { }//构造函数
const Number operator+(const Number& n) const {//重载+法运算符
return Number(i + n.i);//如果右操作数不是Number,可以通过构造函数转化
}
friend const Number operator-(const Number&, const Number&);//通过友元函数重载-法运算符
Number& operator=(const Number&n){
if(this==&n) return *this;
i=n.i;
return *this;
}
void show() {cout<<i<<endl;}
};
const Number operator-(const Number& n1, const Number& n2){
return Number(n1.i - n2.i);//左右操作数都可以通过构造函数转换
}
int main() {
Number a(47), b(11),c;
cout<<"a=";a.show();
cout<<"b=";b.show();
c=a + b; // OK
cout<<"a+b=";c.show();
c=a + 1; // 右操作数转换为Number
cout<<"a+1=";c.show();
//1 + a; // 错误:左操作数不是Number类型
c=a - b; // OK
cout<<"a-b=";c.show();
c=a - 1; //右操作数转换为Number
cout<<"a-1=";c.show();
c=1 - a; //左操作数转换为Number
cout<<"1-a=";c.show();
c=1 - 1; //转换为Number之后操作
cout<<"1-1=";c.show();
return 0;
}
注意事项:
有些运算符(C++中)不能被重载:
::(作用域解析符)、.(成员选择符)、.*(成员指针间接引用符)及?:(条件运算符)
不能定义C++中没有的运算符,且运算符的性质不能改变(本来的意义、优先级和结合性、操作数的个数)。
自增和自减运算符
成员函数重载
const Byte& operator++() { // 前缀++
b++;
return *this;
}
const Byte& operator--() { // 前缀--
--b;
return *this;
}
//前缀形式返回改变后的对象,返回*this
//int参数用来区分前缀和后缀
const Byte operator++(int) { // 后缀++
Byte before(b);
b++;
return before;
}
const Byte operator--(int) { // 后缀--
Byte before(b);
--b;
return before;
}
//后缀形式返回改变之前的值
全局函数重载
const Integer& operator++(Integer& a) { // 前缀
a.i++;
return a;
}
const Integer operator++(Integer& a, int) { // 后缀
Integer before(a.i);
a.i++;
return before;
}
2.重载输入/输出运算符>>/<<
例:一个复数类
#include <iostream>
using namespace std;
class complex{//一个复数类
private:
double real, image;
public:
complex(double r = 0, double i = 0){ real = r; image = i; } //构造函数
const complex operator+(const complex& right) const{//成员函数重载运算符+
return complex (real+right.real,image+right.image);
}
friend ostream& operator<<(ostream& os, const complex& c);//全局函数重载输入输出运算符
friend istream& operator>>(istream& is,complex& c);
};
ostream& operator<<(ostream& os, const complex& c){//重载<<输出运算符
if(c.real==0 && c.image==0){ os << "0"; }
if(c.real!=0){ os << c.real; }
if(c.image!=0){
if(c.image>0 && c.real!=0)
os << "+";
os << c.image << "i" ;
}
return os; //返回ostream对象便于链式表达式
}
istream& operator>>(istream& is, complex& c){
cout<<"please input a complex:";
return is>>c.real>>c.image;
}
int main() {
complex c1,c2;
cin>>c1;
cin>>c2;
cout<<"c1+c2="<<c1+c2<<endl;
}
3.重载赋值运算符
//重载赋值运算符
//并不需要执行任何复制操作,结果就是一个对象被赋值给了它自己。
//可以通过检查赋值语句左侧对象的 this 地址是否和右侧对象的地址相同来测试其可能性
//赋值运算符operator= 只能用成员函数重载
//如果没有定义赋值运算符函数,编译器会自动生成一个
//类类型初始化时调用构造函数,赋值(左操作数已经存在)时调用operator=
Byte& operator=(const Byte& right) {
if(this == &right) return *this;
b = right.b;
return *this;
}
4.重载下标运算符
必须是成员函数,只接受一个参数,返回一个参数的引用。
class vect {
public:
vect(const int a[], int n);
~vect() { delete []p; }
vect& operator=(const vect&v);
int& operator[](int i); // 重载下标运算
private:
int* p;
int size;
};
vect::vect(const int a[], int n) : size(n){
assert(size > 0);
p = new int[size];
for(int i=0; i<size; ++i)
p[i] = a[i];
}
vect& vect::operator=(const vect& v){
if (this != &v) {
assert(v.size == size);//只允许相同大小的数组赋值
for(int i =0; i<size; ++i)
p[i] = v.p[i];
}
return *this;
}
int& operator[](int i){
assert(i>=0&&i<size)
return p[i];
}
int main() { //测试程序
int a[5]={1,2,3,4,5};
vect v1(a,5);
v1[2]=9;//调用operator[]
}
5.重载类型转换函数
operator type运算符,将类类型转换为type类型。
只能用成员函数重载。不带参数。
如前面提到的operator double函数
可能引起二义性问题
#include<bits/stdc++.h>
using namespace std;
class X; // 类声明
class Y {
int y;
public:
//Y(X); // X到Y的转换
int get_y(){return y;}
void sta(){cout<<"class Y!"<<" y="<<y<<endl;}
};
class X {
int x;
public:
X(int nx=5):x(nx){}
void stat(){cout<<"class X!"<<" x="<<x<<endl;}
operator Y(){Y n;return n;} // X到Y的转换
};
Y f(Y){Y n;return n;}
int main() {
X a;
a.stat();
Y b=f(a);
return 0;
}
//二义性 operator Y() const;Y(X); 存在二义性问题 目标不明确
重载Complex复数类
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(){real=0;imag=0;}
Complex(double r,double i){real=r; imag=i;}
const Complex operator-();
const Complex operator+();
Complex& operator+=(const Complex &c2);
const Complex operator+(const Complex &c2);
const Complex operator-(const Complex &c2);
friend Complex operator*(Complex &c1, Complex &c2);
const Complex operator/(const Complex &c2);
friend ostream& operator<<(ostream& os, const Complex& c);
friend istream& operator>>(istream& is,Complex& c);
void show();
private:
double real;
double imag;
};
const Complex Complex::operator-()
{
return Complex(-real,-imag);
}
const Complex Complex::operator+()
{
return Complex(real,imag);
}
Complex& Complex::operator+=(const Complex &c2)
{
real+=c2.real;
imag+=c2.imag;
return *this;
}
const Complex Complex::operator+(const Complex &c2)
{//复数相加
Complex c;//定义一个新的对象
c.real=real+c2.real;
c.imag=imag+c2.imag;
return c;
}
const Complex Complex::operator-(const Complex &c2)
{//复数相减
Complex c;
c.real=real-c2.real;
c.imag=imag-c2.imag;
return c;
}
Complex operator*(Complex &c1, Complex &c2)
{//复数相乘
Complex c;
c.real=c1.real*c2.real-c1.imag*c2.imag;
c.imag=c1.imag*c2.real+c1.real*c2.imag;
return c;
}
const Complex Complex::operator/(const Complex &c2)
{//相除
Complex c;
c.real=(real*c2.real+imag*c2.imag)/(c2.real*c2.real+c2.imag*c2.imag);
c.imag=(imag*c2.real-real*c2.imag)/(c2.real*c2.real+c2.imag*c2.imag);
return c;
}
void Complex::show()
{
if(imag>0)
cout<<real<<'+'<<imag<<'i'<<endl;
else if(imag==0)
cout<<real<<endl;
else cout<<real<<imag<<'i'<<endl;
}
ostream& operator<<(ostream& os, const Complex& c){
if(c.real==0 && c.imag==0){ os << "0"; }
if(c.real!=0){ os << c.real; }
if(c.imag!=0){
if(c.imag>0 && c.real!=0)
os << "+";
os << c.imag << "i" ;
}
return os; //返回ostream对象便于链式表达式
}
istream& operator>>(istream& is, Complex& c){
cout<<"please input a complex:";
return is>>c.real>>c.imag;
}
int main()
{
int a,b,c,d;
cout<<"enter two complex,"<<"c1_real:";
cin>>a;
cout<<"c1_imag:";cin>>b;
cout<<"c2_real:";cin>>c;
cout<<"c2_imag:";cin>>d;
Complex c1(a,b),c2(c,d);
cout<<"c1="<<c1<<endl;
cout<<"c2="<<c2<<endl;
cout<<"c1+c2="<<c1+c2<<endl;
cout<<"c1-c2="<<c1-c2<<endl;
cout<<"c1*c2="<<c1*c2<<endl;
cout<<"c1/c2="<<c1/c2<<endl;
return 0;
}
四、继承与派生
1.基类和派生类
继承:保持已有的类的特性而构造心的类。被继承的已有类称为基类。
继承的目的:实现代码重用。
可分为单继承(只有一个基类)和多继承(有两个或两个以上基类)。
派生:在已有类的基础上新增自己的特性而产生心的类。派生出的新类称为派生类。
派生的目的:当原有程序不能解决所有问题,需要对原有程序进行改造。
派生类的三种继承方式:
Private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问。任何其它类或者外部代码都不能访问。
Protected:可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问(对建立其所在类对象的模块来说,它与 private 成员的性质相同。对于其派生类来说,它与 public 成员的性质相同。既实现了数据隐藏,又方便继承,实现代码重用。)
Public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
注:友元函数包括两种:设为友元的全局函数,设为友元类中的成员函数
派生类的对象和派生类中的成员函数对基类的访问是不同的
class <派生类名>:<继承方式><基类名>{
<新增成员说明>
};
例:
class A{
int i;
A(int x=0){i=x;}//构造函数
};
class B:public A{
int j;
B(int a,int b){A(a);j=b;}//派生类的构造函数
};
B b(1,2);
构造函数和析构函数不能被继承:
派生类的构造函数应该同时对基类和派生类进行初始化。
执行顺序:基类构造函数->子对象构造函数->派生类函数体
派生类的析构函数听该包含着基类的析构函数。
执行顺序(与构造函数相反):派生类析构函数的函数体->子对象所在类的析构函数->基类析构函数
2.单继承
派生类构造函数定义格式
<派生类构造函数名>(<总参数表>):<基类构造函数名>(<参数表>),<其它初始化项>{
<派生类自身数据成员初始化>
}
例:
class A{
public:
A(int i){a=i;}
void print(){cout<<a;}
int geta(){return a;}
private:
int a;
};
class B:public A{
public:
B(int i,int j,int k):A(i),aa(j){//派生类构造函数
b=k;
}
void print(){
A::print();
cout<<b<<aa.geta()<<endl;
}
private:
int b;
A aa;
};
B bb(1,2,3);
bb.print();
子类型: 一个类型B中包含了另一个类A的所有行为。公有继承下,派生类B是基类A的子类型,称B适应于A。
赋值兼容规则:当类型B是类型A的子类型时,B的对象可以赋给A的对象(A=B),B的对象可以给A类对象引用赋值(&A=B),B类对象地址可以给A类对象指针赋值(*A=B)。
赋值兼容规则(A:private a;B:private b):
A a1(10),a2;
B b(10,20);
a2=b;
3.多继承
在实现时多加一个参数表,其余与单继承一样
class C:public A,public B{
public:
C(int i,int j,int k):B(i),A(j){
c=k;
)
void print(){
A::print();
B::print();
cout<<c;
}
private:
int c;
};
多继承的二义性问题:
1.调用不同基类的相同成员
class A{
public:
void f();
};
class B{
public:
void f();
};
class C:public A,public B{
}:
C c;
c.f();//出现二义性问题
c.A::f();//解决
2.派生类的基类具有相同的基类
例:
class B{
public:
int b;
};
class B1:public B{
};
class B2:public B{
};
class C:public B1,public B2{
};
C c;
c.b;//有二义性
c.B1::b;//解决
4.虚基类
主要用于解决多继承时可能发生的对同一基类多次继承的二义性问题。
为最远的派生类提供唯一的基类成员,不重复产生多次拷贝。
例:
class B{
public:
int b;
};
class B1:virtual public B{//使用virtual关键字修饰
};
class B2:virtual public B{
};
class C:public B1,public B2{
};
C c;
c.b;//无二义性
虚基类的构造函数:
在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。
D1(int a) : B0(a), B1(a), B2(a){}
五、多态性与虚函数
多态性是指用同一个名字定义不同的函数,即“一个接口,多种方法”(面向对象程序设计的精华)
多态性的实现:
1.编译时多态性
函数重载和运算符重载、模板
2.运行时多态
借助虚函数来获得
1.联编
源程序经过编译、连接,成为可执行代码的过程称为联编。目的是建立函数调用与函数体之间的联系即将一个函数调用连接到一函数的入口地址。
两种联编方式:
1.静态联编,即编译时联编。程序执行快,但灵活性小。
2.动态联编,即运行时联编。灵活性高,但程序执行慢。
是C++实现运行时多态的关键因素。
2.虚函数与多态类
带有virtual关键字的成员函数成为虚函数。虚函数是成员函数,且是非static的成员函数。虚函数是动态联编的基础。包含虚函数的类称为多态类。
**只有当访问虚函数是通过基类指针时才可以获得运行时的多态性。**使用对象名和点算符调用时是静态联编方式。
例:定义一个Vehicle类
#include<iostream>
using namespace std;
class Vehicle{
public:
bool status;
virtual void run(){
cout<<"Vehicle run\n";
status=true;
}
virtual void stop(){
cout<<"Vehicle stop\n";
status=false;
}
};
class Bicycle:public Vehicle{
private:
bool status;
public:
void run(){
cout<<"Bicycle run\n";
status=true;
}
void stop(){
cout<<"Bicycle stop\n";
status=false;
}
};
class Motorcar:public Vehicle{
private:
bool status;
public:
void run(){
cout<<"Motorcar run\n";
status=true;
}
void stop(){
cout<<"Motorcar stop\b";
status=false;
}
};
int main()
{
Vehicle *a;//定义一个基类指针
Bicycle b;
Motorcar d;
b.run();
b.stop();
a=&b;
a->run();//通过指针调用
a=&d;
a->run();
}
运行结果:
Bicycle run
Bicycle stop
Bicycle run
Motorcar run
3.纯虚函数与抽象类
纯虚函数是在基类中声明但是没有定义的虚函数,设置函数值等于0。
通过将虚函数声明为纯虚函数可以强制在派生类中重新定义虚函数。
包含有纯虚函数的类称为抽象类。不能说明抽象类的对象,但能说明指向抽象类的指针,抽象类指针用来指向该抽象类的派生类的对象。一个抽象类只能作为基类派生其它的类。
例:
/*下面的shape 类是一个表示形状的抽象类,area() 为求图形面积的函数。从
shape 类派生三角形类和圆类,并实现求面积函数:*/
#include<iostream>
using namespace std;
class Shape{
public:
virtual double area()=0;//纯虚函数
};
class Triangle:public Shape{
double length,width;
public:
void set(double new_l,double new_w){
length=new_l;
width=new_w;
}
double area(){
return 1.0/2.0*length*width;
}
};
class Circle:public Shape{
double radius;
public:
void set(int new_r){
radius=new_r;
}
double area(){
return 3.14159*radius*radius;
}
};
int main(){
Shape s;
Triangle a;
s=&a;
cout<<"设置三角形a的底为5,高为7....\n";
a.set(5,7);
cout<<"triangle a 的面积为:"<<s->area()<<endl;
Circle b;
cout<<"设置圆b的半径为6....\n";
b.set(6);
cout<<"circle b 的面积为:"<<b.area()<<endl;
}
运行结果:
设置三角形a的底为5,高为7....
triangle a 的面积为:17.5
设置圆b的半径为6....
circle b 的面积为:113.097
虚析构函数:当可能通过基类指针删除派生类对象时需要虚析构函数。
4.模板
C++中的模板提供了重用源代码的方法,C++的库是基于模板的技术
两种类型的模板
类模板
函数模板
template <class T>
T max( T a, T b) {
return a > b ? a : b;
}
void main( ) {
cout << "max(20, 30) = " << max(20, 30) << endl;
cout << "max('t', 'v') = " << max('t', 'v') << endl;
cout << "max(10.1, 15.2) = " << max(10.1, 15.2) << endl;
}
C++标准模板库(STL库)
包括组件:容器、迭代器、算法
STL容器:
常用容器:vector,deque,list,map/multimap,set
特殊容器:stack,queue,priority_queue
其它容器:hashtable
STL算法:搜寻、排序、拷贝、数值运算
STL迭代器:在对象集合内进行遍历
六、输入输出流
在计算机内存中,数据从内存的一个地址移动到另一个地址称为数据流动——流操作。
流操作是通过缓冲区(buffer)机制实现的。
缓冲区(buffer):内存的一块区域——用作文件与内存交换数据。
头文件:iostream(输入\输出流)\iomanip(格式控制)\fstream(处理文件信息)
1.输入\输出流
输入/输出运算符有两个运算分量,左边为输入/输出流ostream对象,右边为一个基本输入/输出类型数据。
可以重载“<<”,“>>”
格式:
//<<必须重载为友元函数
ostream &operator<<(ostream &os,point p){
<类的对象的输出操作>
os<<p.x<<p.y;
return os;//能连续输出多个对象
}
//>>必须重载为友元函数
//第二个形参为引用
istream &operator>>(istream &is,point &p){
<类的对象的输入操作>
is>>p.x;
is>>p.y;
return is;
}