结构化程序设计
程序=数据结构+算法
结构化程序设计中,函数和其所操作的数据结构没有直观联系
随着程序规模增加,难以理解
**重用:**在编写某个程序时,发现其需要的某项功能,在现有的某个程序里有了相同或类似的实现,那么就会希望抽取出来
结构化程序难以重用
面向对象的程序设计
面向对象的程序=类+类+……+类
设计程序的过程实际时设计类的过程
- 面向对象的程序设计方法:
- 将某类客观事物共同特点(属性)归纳出来,形成一个数据结构(可以用多个变量描述书屋的属性)
- 将这类事物所能进行的行为也归纳出来,形成一个个函数,这个函数可以用来操作数据结构(这一步叫抽象)
- 然后,通过某种语法形式,将数据结构和操作该数据结构的函数”捆绑“在一起,形成一个类,从而使数据结构和操作该数据结构的算法呈现出显而易见的紧密关系,这就是封装
- 面向对象的程序设计都具有抽象、封装、继承和多态四个基本特点。
类和对象
- 从客观事物抽象出类
-
写一个程序,输入矩形的长和宽,输出面积和周长
- 抽象出”矩形“这个东西
- 矩形的属性使长和宽
- 矩形的行为是,设置长宽、算面积和算周长这三种行为
- 这三种行为,可以各用一个函数来实现
-
将长、宽变量,以及三个行为函数封装在一起,就能形成一个”矩形类“
-
长、宽变量称为该”矩形类“的成员变量
-
三个函数称为该类的成员函数
-
成员变量和成员函数统称为类的成员
-
类就成了一个带函数的结构
#include<iostream>
using namespace std;
class CRectangle
{
public:
int w,h;//成员变量
int Area()
{
return w*h;
}
int Perimeter()
{
return 2*(w+h);
}
void Init(int w_,int h_)
{
w=w_;
h=h_;
}//初始化,设置宽和高
};//必须有分号
int main()
{
int w,h;
CRectangle r;//r是一个对象
cin>>w>>h;
r.Init(w,h);
cout<<r.Area()<<endl<<r.Perimeter();
return 0;
}
- 通过类,可以定义变量。类定义出来的变量,也称为类的实例,就是我们所说的对象
- C++中,类的名字就是用户自定义的类型的名字。可以像使用基本类型那样使用它
- CRectangle 就是一种用户自定义的类型
和结构变量一样,对象所占用的内存空间的大小=所有成员变量的大小之和
每个对象各有自己的存储空间。一个对象的某个成员变量被改变了,不会影响到另一个对象
- 对象间的运算:
- 和结构变量一样,对象之间可以用“=”进行赋值
- 但不能用“==”,“!=”,“>”,"<"等进行比较,除非这些运算符经过了重载
-
使用类的成员变量和成员函数
-
对象名.成员名
r1.w=5;
r2.Init(5,4);
-
指针->成员名
CRectangle r1,r2;
CRectangle *p1=& r1;
CRectangle *p2=& r2;
p1->w=5;
p2->Init(5,4);
-
引用名.成员名
CRetangle r2;
CRectangle &rr=r2;
rr.w=5;
-
类的成员函数和类的定义分开写
class CRectangle
{
public:
int w,h;//成员变量
int Area();
int Perimeter();
void Init(int w_,int h_);
};//必须有分号
int CRectangle::Area()
{
return w*h;
}
int CRectangle::Perimeter()
{
return 2*(w+h);
}
void CRectangle::Init(int w_,int h_)
{
w=w_;
h=h_;
}//初始化,设置宽和高
类成员的可访问范围
- private:私有成员,只能在成员函数内访问
- public:公有成员,可以在任意地方访问
- protected:保护成员
以上三种关键字出现的次数和先后次序都没有限制
class className
{
private:
私有属性和函数
public:
公有属性和函数
protected:
保护属性和函数
};
//若某个成员前面没有上述关键字,则
缺省地被认为是私有成员
class Man
{
int nAge;
char szName[20];//缺省地被认为是私有成员
public:
void SetName(char* szName)
{
strcpy(Man::szName,szName);
}
};
- 在类的成员函数内部,能够访问:
- 当前对象的全部属性。函数;
- 同类其他对象的全部属性、函数。
- 在类的成员函数以外的地方,只能够访问该类对象的公有成员
class CEmployee
{
private:
char szName[30];//名字
public:
int salary;//工资
void setName(char *name);
void getName(char *name);
void averageSalary(CEmployee e1,CEmployee e2);
};
void CEmployee::setName(char *name)
{
strcpy(szName,name);//ok
}
void CEmployee::getName(char *name)
{
strcpy(name,szName);//ok
}
void CEmployee::averageSalary(CEmployee e1,CEmployee e2)
{
cout<<e1.szName;
salary=(e1.salary+e2.salary)/2;
}//在自己本身类里面,不管对象是不是同一个,都可以访问所有成员
int main()
{
CEmployee e;
strcpy(e.szName,"Tom1234567889");
//编译出错,main()属于成员函数外部,不能访问私有成员
e.setName("Tom");
e.salary=5000;
return 0;
}
- 设置私有成员的机制,叫隐藏
- 隐藏的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员函数即可。否则所有直接访问成员变量的语句都需要修改
成员函数的重载及参数缺省
#include<iostream>
using namespace std;
class Location
{
private:
int x,y;
public:
void init(int X=0,int Y=0)
{
x=X;
y=Y;
}
//缺省值为0;
void valueX(int val){x=val;}
int valueX(){return x;}
//valueX的重载
};
int main()
{
Location A,B;
A.init(5);
cout<<A.valueX()<<endl;
A.valueX(7);
cout<<A.valueX()<<endl;
}
构造函数
- 成员函数的一种
- 名字与类名相同,可以有参数,不能有返回值(void也不行)
- 作用是对对象进行初始化,如给成员变量赋初始值
- 如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数
- 默认构造函数无参数,不做任何操作
- 对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数
- 一个类可以有多个构造函数
- 构造函数的作用:
- 无需专门写初始化函数,也不用担心忘记调用初始化函数
- 避免因没有初始化就使用,导致程序出错
//无构造函数
#include<iostream>
using namespace std;
class Complex
{
private:
double real,imag;
public:
void Set(double r,double i=0);
};//编译器自动生成默认构造函数
Complex c1;
Complex* pc=new Complex;//默认构造函数被调用
//构造函数
class Complex
{
private:
double real,imag;
public:
Complex(double r,double i=0){
real=r;
imag=i;
}//i缺省值为0,两个重载版本
}
Complex c1;//error,缺少构造函数的参数
Complex c2(2);//ok
Complex c1(2,3);//等等
Complex *pc=new Complex(3,4);
//多个构造函数,参数个数或类型不同,形成重载关系
class Complex
{
private:
double real,imag;
public:
void Set(double r,double i);
Complex(double r,double i)
{
real=r;
imag=i;
}
Complex(double r){
real=r;
imag=0;
//在为0的情况下,和i的缺省值为0是相同情况
}
Complex(Complex c1,Complex c2){
real=c1.real+c2.real;
imag=c1.imag+c2.imagl
}
}
Complex c1(3);
Complex c2(1,0);
Complex c3(c1,c2);
构造函数在数组中的使用
#include<iostream>
using namespace std;
class CSample
{
int x,y;//缺省地认为是私有成员
public:
CSample()
{
cout<<"Constructor 1 Called"<<endl;
}
CSample(int n,int m)
{
x=n;
y=m;
cout<<"Constructor 2 Called"<<endl;
}
};
int main()
{
CSample array1[2];
//这个数组没有初始化值,默认执行无参的构造函数
//因此输出两次"Constructor 1 Called"
cout<<"step1"<<endl;
CSample array2[2]={{1,4},{5,1}};
//输出两次"Constructor 2 Called"
//初始值分别为4,5
cout<<"step2"<<endl;
CSample array3[2]={{3,1}};
//输出一次"Constructor 2 Called"
//输出一次"Constructor 1 Called"
cout<<"step3"<<endl;
}
#include<iostream>
using namespace std;
class Test
{
public:
Test(int n){ }
Test(int n,int m){ }
Test(){ }
};
int main()
{
//Test array1[3]={1,{1,2},3};
Test array1[3]={1,Test(1,2),3};
//初始值多个的方法
Test* pArray[3]={new Test(4),new Test(1,2)};
//指针数组,每个方格里都是一个指针
//pArray[0],这里的指针指向初始值为4的对象
}
复制构造函数
只有一个参数,即对同类对象的引用
一定要是引用!!!
形如X::X( X& )或X: :X(const X & ),二者选一
通常使用常量对象作为参数
如果没有定义复制构造函数,编译器也是生成默认复制构造函数
完成复制功能
//无复制构造函数
class Complex
{
private:
double real,imag;
};
Complex c1;//调用缺省无参构造函数
Complex c2(c1);//调用缺省的复制构造函数,将c2初始化成c1
//自写复制效果
class Complex
{
public:
double real,imag;
Complex(){ }//无参的构造函数
Complex(const Complex & c)
{
real=c.real;
imag=c.imag;
cout<<"Copy Constructor called"<<endl;
}
};
Complex c1;//调用自写的无参构造函数
Complex c2(c1);//调用自写的复制构造函数
作用
1.当用一个对象去初始化同类的另一个对象时
Complex c2(c1);
Complex c2=c1;//与上一条语句时等价的
2.如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用
Class A
{
public:
A(){ };
A(A & a)
{
cout<<"Copy constructor called"<<endl;
}
};
void Func(A a1){ }
//在这个函数里的对象a1,调用的是复制构造函数,将a1复制成a2
int main()
{
A a2;//在这里的对象定义,调用的是构造函数
Func(a2);
return 0;
}
注意点:
1.这个时候,如果你自己写的复制构造函数,并没有进行复制的操作
2.那么这个时候,形参并不是实参的一个拷贝
3.如果强行要输出未拷贝形参的值,将会是一个默认的随机值(错误值
#include<iostream>
using namespace std;
class Complex
{
public:
double real,imag;
Complex(int x,int y){real=x;imag=y;}//无参的构造函数
Complex(const Complex & c)
{
cout<<"Copy Constructor called"<<endl;
}
};
void Func(Complex a1)
{
cout<<"Func is going"<<endl;
cout<<a1.real<<" "<<a1.imag<<endl;
//输出一个0和一个无穷小
}
int main()
{
Complex c1(3,5);
Func(c1);
}
3.如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用
Class A
{
public:
int v;
A(int n) {v=n;};
A(const A & a)
{
v=a.v;
cout<<"Copy constructor called"<<endl;
}
};
A Func()
{
A b(4);
return b;
//这个返回类A的返回值,其实是一个调用复制构造函数的过程
}
int main()
{
cout<<Func().v<<endl;
return 0;
}
注意:对象间的赋值并不会导致复制构造函数被调用
c2=c1;//对象间的赋值,不会调用复制构造
A c3(c1);//这个时候,才是复制构造!!!
常量引用参数的使用
可以避开调用复制构造函数的开销
void fun(CMyclass obj_)
{
cout<<"fun"<<endl;
}
- 这样调用需要引发复制构造构造函数调用,开销比较大
- 可以考虑使用CMyclass & 引用作为参数
- 如果希望确保实参的值在函数中不应被改变,那么可以加上const关键字
Question:为什么要自己写复制构造函数
类型转换构造函数
- 目的:实现类型的自动转换
- 只有一个参数,而且不是复制构造函数的话,就可以看作是转换构造函数
- 当需要的时候,编译系统会自动调用转换构造函数,建议一个无名的临时对象(或临时变量)
public:
double real,imag;
Complex(int x,int y){real=x;imag=y;}//类型转换构造函数
//会使得初始出来的,real imag的类型,都是int
#include<iostream>
using namespace std;
class Complex
{
public:
double real,imag;
Complex(int i)
{
cout<<"IntConstructor called"<<endl;
real=i;
imag=0;
}//类型转换构造函数
Complex(double r,double i)
{
real=r;
imag=i;
}
};
int main()
{
Complex c1(7,8);
Complex c2=12;
c1=9;//9被自动转换成一个临时Complex对象
cout<<c1.real<<" "<<c1.imag<<endl;
return 0;
}
析构函数
名字与类名相同
在前面加‘~’
没有参数和返回值
一个类最多只有一个析构函数
析构函数对象消亡时自动被调用。可以用析构函数在对象消亡前进行善后工作,比如释放分配的空间
没写——自动生成缺省的析构函数,啥也不干
class my_string
{
private:
char *p;
public:
my_string()
{
p=new char[10];
}//无参的初始化构造函数
~ my_string()
{
delete []p;
}
}
一般来说,先构造的后析构
int main()
{
A *p=new A[2];
A *p2=new A;
A a;
delete []p;
}
!!!一共调用析构函数三次
new出来的对象,只有delete才会消亡