面向对象程序 = 类 + 类 + 类 + …
将某类客观事物共同特点归纳出来形成一个数据结构
将这些事物所能进行的行为也归纳出来,形成一个个函数,这些函数可以用来操作数据结构(这一步叫做抽象)
通过某种语法形式,将这些数据结构和函数捆绑在一起,形成一个“类”。
类和对象
写一个程序,输入矩形长和宽,输出面积和周长
矩形的属性:长和宽
行为:计算面积,计算周长,设置长和宽
长,宽成为 矩形类的成员变量,三个函数成为成员函数
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;
cin >> w >>h;
r.Init(w,h);
cout<<r.w<<endl;
cout<<r.Area()<<endl<<r.Perimeter();
return 0;
}
和结构变量一样,对象所占用的内存空间大小等于所有成员变量的大小之和
对于上面的CRectanlge类, sizeof(CRectangle) = 8(因为有两个int类型的成员变量)
CRectangle r1,r2;
CRectangle * p1 = &r1;
CRectangle *p2 = &r2;
p1->w = 5;
p2->Init(5,4);
类成员函数
//stock00.h
#include<iostream>
#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 = share * share_val;}
public:
void acquire(const std::string & co, long n, double pr);
void buy(long num,double price);
void update(double price);
void show();
};
#endif
关键字private和public描述了对类成员的访问控制,使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数(或者友元函数)
类设计尽可能将公有接口与实现细节分开,公有接口表示设计的抽象组件,将实现细节放在一起并将它们与抽象分开被称为封装
成员函数的定义与常规函数定义非常相似,它们有函数头和函数体,也可以有返回类型和参数,但是他们有两个特殊的特征:
- 定义成员函数时,使用作用域解析运算符( :: ) 来标识函数所属的类
- 类方法可以访问类的private组件
首先,成员函数的函数头使用作用域解析运算符(::)来指出函数所属的类,例如,update()成员函数的函数头如下:
void Stock::update(double price)
构造函数
C++中public、protected及private用法
构造函数
成员函数的一种,执行必要的初始化工作
- 名字与类名字相同,可以有参数,不能有返回值(void也不行)
- 作用是对对象进行初始化,如给成员变量赋初值
- 如果定义类时没有写构造函数,则编译器生成一个默认的无参数的构造函数
(默认构造函数无参数不做任何操作) - 对象生成时构造函数自动被调用,对象一旦生成,就再也不能在其上执行构造函数
- 一个类可以有多个构造函数
class Complex{
private:
double real,imag;
public:
void Set(double r,double i);
}; //编译器自动升成默认构造函数
Complex c1; //默认构造函数被调用
Complex *pc = new Complex; //默认构造函数被调用
class Complex{
private:
double real,imag;
public:
Complex(double r,double i = 0);
};
//构造函数
Complex::Complex(double r,double i){
real=r;imag=i;
}
Complex c1; //报错,缺少构造函数的参数
Complex *pc = new Complex; //报错,缺少参数
Complex c1(2); //OK
Complex * pc = new Complex(3,4);
整形可以自动转化为double类型
一个类可以有多个构造函数
class Complex{
private:
double real,imag;
public:
void Set(double r,double i);
Complex(double r,double i);
Complex(double r);
Complex(Complex c1, Complex c2);
};
Complex::Complex(double r,double i){
real=r;imag=i;
}
Complex::Complex(double r)
{
real = r;imag = 0;
}
Complex::Complex(Complex c1,Complex c2)
{
real = c1.real+c2.real;
imag = c1.imag +c2.imag;
}
Complex c1(3),c2(1,0),c3(c1,c2);
//c1={3,0},c2={1,0},c3={4,0}
构造函数在数组中的使用
Class CSample{
int x;
public:
CSample(){
cout<<"Constructor 1 Called"<<endl;
}
CSample(int n){
x = n;
cout<<"Constructor 2 Called"<<endl;
}
};
int main(){
CSample array1[2];
cout<<"step1"<<endl;
CSample array2[2] = {4,5};
cout<<"step2"<<endl;
CSample array3[2] ={3};
cout<<"step3"<<endl;
CSample *array4 = new CSample[2];
delete []array4;
return 0;
}
输出:
Constructor 1 Called
Consturctor 1 Called
step1
Constructor 2 Called
Constructor 2 Called
step2
Constructor 2 Called
Constructor 1 Called
step 3
Constructor 1 Called
Constructor 1 Called
class
{
public://下面是三个构造函数
Test(int n){} //(1)
Test(int n,int m){} //(2)
Test(){} //(3)
};
Test array1[3] = {1,Test(1,2)}; //array1中的三个元素分别使用(1)(2)(3)初始化
Test * parray2[3] = {new Test(4),new Test(1,2)}; //new的返回值是地址,这条语句只生成了两个对象
//而不是三个,分别用了(1)(2),因为parray[2]我们没有初始化它,这个仅仅只是一个指针,而且不知道指向哪里
复制构造函数
- 只有一个参数,即对同类对象的引用
- 形如 X::X(X&) 或者 X::X(const X &) , 二者选一,后者能以常量对象作为参数
- 如果没有定义复制构造函数,那么编译器生成默认复制构造函数,默认的复制构造函数完成复制功能
class Complex{
private:
double real,imag;
};
Complex c1; //调用缺省无参构造函数
Complex c2(c1); //调用缺省的复制构造函数,将c2初始化成和c1一样
一个类只有一个复制构造函数,要么是你自己写的,要么是编译器帮你生成的
class Complex{
private:
double real,imag;
//自己写了一个无参构造函数
Complex(){}
//自己写了一个复制构造函数
Complex(const Complex & c){
real = c.real;
imag = c.imag;
cout<<"copy Constructor called";
}
};
Complex c1;
Complex c2(c1);
不允许有形如 X::X(X) 的构造函数
复制构造函数起作用的三种情况:
- 当用一个对象去初始化同类的另一个对象时;
Complex c2(c1);
Complex c2 = c1; //和上面那一条语句是等价的,但是是初始化语句,不是赋值语句
- 如果某个函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用
class A
{
public:
A(){};
A(A & a){
cout<<"copy constructor called"<<endl;
}
};
C++规定,如果赋值函数的参数是对象的话,使用复制构造函数赋值
void Func(A a1){}
int main()
{
A a2;
Func(a2);
return 0;
}
- 如果函数的返回值是类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;
}
int main(){
cout<<Func.v<<endl;
return 0;
}
对象间的赋值并不导致赋值构造函数被调用
类型转换构造函数
- 定义转换构造函数的目的是实现类型的自动转换
- 只有一个参数,而且不是复制构造函数的构造函数,一般就可以看做是转换构造函数
- 当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)
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 String{
private:
char *p;
public:
String(){
p = new char[10];
}
~ String();
};
String :: ~String()
{
delete []p;
}
析构函数和数组
对象数组的生命期结束后,对象数组的每个元素的析构函数都会被调用
class Ctest{
public:
~Ctest(){cout<<"destructor called"<<endl;}
};
int main()
{
Ctest array[2];
cout<<"End main"<<endl;
return 0;
}
main结束时array中的对象会消亡,调用析构函数
delete 函数导致析构函数的消亡
class Ctest{
public:
~Ctest(){cout<<"destructor called"<<endl;}
};
Ctest * pTest;
--------------------------------
pTest = new Ctest;
delete pTest;
--------------------------------
pTest = new Ctest[3]; //构造函数调用三次
delete []pTest; //析构函数调用三次
析构函数在对象作为函数返回值返回后被调用
class CMyclass{
public:
~CMyclass(){cout<<"destructor"<<endl;}
};
CMyclass obj;
CMyclass fun(CMyclass sobj){ //参数对象消亡也会导致析构函数调用
return sobj; //函数调用返回时生成临时对象返回
}
int main()
{
obj = fun(obj); // 函数对象调用的返回值(临时对象)被调用过后,该临时对象析构函数被调用
return 0;
}