回顾例子24的继承,类A的是showx(),派生的类AX是showy(),是两个不同名函数。但实际中有时候会出现重名情况。比如基类A里的函数名叫show(),在派生类里自定义函数也叫show(),起了冲突。但,我们可以通过类来区分。
#include <iostream>
using namespace std;
class A{
public:
A(){ }
~A(){ }
void show()
{
cout<<"xxxxxxxxxxxxxxx"<<endl;
}
};
class AX:public A{
public:
void show()
{
cout<<"yyyyyyyyyyyyyyy"<<endl;
}
};
int main()
{
A a;
a.show();
AX b;
b.show();
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ base1_3.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
xxxxxxxxxxxxxxx
yyyyyyyyyyyyyyy
@ubuntu:/mnt/hgfs/ub2$
这个容易理解,然后有人就想尝试能不能只通过一种类声明一个对象,然后通过不同指针来区分基类跟派生类。
#include <iostream>
using namespace std;
class A{
public:
A(){ }
~A(){ }
void show()
{
cout<<"xxxxxxxxxxxxxxx"<<endl;
}
};
class AX:public A{
public:
void show()
{
cout<<"yyyyyyyyyyyyyyy"<<endl;
}
};
int main()
{
//派生类的对象a
AX a;
AX *q = &a;
A *p = &a;
//基类的对象b
A b;
AX *x = &b;
A *y = &b;
}
测试结果
@ubuntu:/mnt/hgfs/ub2$ g++ base1_4.cpp
base1_4.cpp: In function ‘int main()’:
base1_4.cpp:32:13: error: invalid conversion from ‘A*’ to ‘AX*’ [-fpermissive]
AX *x = &b;
^~
@ubuntu:/mnt/hgfs/ub2$
说明:基类的指针可以指向派生类的对象,而派生类的指针是不能指向基类对象的
于是,通过指针尝试打印show()
#include <iostream>
using namespace std;
class A{
public:
A(){ }
~A(){ }
void show()
{
cout<<"xxxxxxxxxxxxxxx"<<endl;
}
};
class AX:public A{
public:
void show()
{
cout<<"yyyyyyyyyyyyyyy"<<endl;
}
};
int main()
{
AX a;
AX *q = &a;
A *p = &a;
q->show();
p->show();
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ base1_5.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
yyyyyyyyyyyyyyy
xxxxxxxxxxxxxxx
@ubuntu:/mnt/hgfs/ub2$
从而可以得出结论:指针访问成员的时候,主要由指针类型来决定,和指向的类无关
案例中都是指向派生类对象a,但决定show()类型的是指针类型,p是基类A所以p的show()对应打印基类的x,跟派生类对象a的无关。
现在有个特殊的需求,要求把基类的show()作为初始化,后续的派生类改动将对基类进行改动,这时就需要关键字:
virtual //修饰表示虚函数
//虚函数:基类指针可指向派生类对象,动态联编
案例如下
#include <iostream>
using namespace std;
class A{
public:
A(){ }
~A(){ }
virtual void show()
{
cout<<"xxxxxxxxxxxxxxx"<<endl;
}
};
class AX:public A{
public:
void show()
{
cout<<"yyyyyyyyyyyyyyy"<<endl;
}
};
int main()
{
AX a;
AX *q = &a;
A *p = &a;
q->show();
p->show();
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ base1_6.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
yyyyyyyyyyyyyyy
yyyyyyyyyyyyyyy
@ubuntu:/mnt/hgfs/ub2$
在这种情况下,就相当于和所指向对象的类有关,跟指针的类型无关
像例中那样,虽然指针p是基类A的数据类型,但指向的a是派生类AX声明的对象,所以最终执行的是对象a的类型。
要求该函数适用各种图形:圆、三角形、正方形,然后把三个图形周长相加。
#include <iostream>
using namespace std;
class shape{
public:
virtual double getC(void){
}
};
class Cir:public shape{
public:
Cir(double ri):r(ri) { }
double getC(void)
{
return 2*3.14*r;
}
private:
int r;
};
class Tri:public shape{
public:
Tri(double a, double b, double c):e1(a),e2(b),e3(c){ }
double getC(void)
{
return e1+e2+e3;
}
private:
double e1;
double e2;
double e3;
};
class Rec: public shape{
public:
Rec(double e)
{
this->e = e;
}
double getC(void)
{
return 4*e;
}
private:
double e;
};
double countC(shape *arr[], int n)
{
double sum = 0;
for(int i=0; i<n; i++)
{
sum += arr[i]->getC();
}
return sum;
}
int main()
{
Cir c(1);
Rec r(3);
Cir c1(2);
Tri t(3,3,3);
shape *arr[] = {&c, &r, &c1, &t};
cout << "total C: "<<countC(arr, 4) << endl;
}
运行结果:
@ubuntu:/mnt/hgfs/ub2$ g++ shape1.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
total C: 39.84
@ubuntu:/mnt/hgfs/ub2$
shape是基类,用于对各个图形的周长进行求和,而每个图形求和方式不一(三角形、圆、正方形),所以通过派生类的方式实现。以上处理形式就是多态。
例中的虚函数没内容
virtual double getC(void){
}
可以写成纯虚函数
virtual double getC(void) = 0;
写成纯虚函数的话可以避免直接拿类shape声明对象,比如
#include <iostream>
using namespace std;
class shape{
public:
virtual double getC(void){
}
};
class Cir:public shape{
public:
Cir(double ri):r(ri) { }
double getC(void)
{
return 2*3.14*r;
}
private:
int r;
};
class Tri:public shape{
public:
Tri(double a, double b, double c):e1(a),e2(b),e3(c){ }
double getC(void)
{
return e1+e2+e3;
}
private:
double e1;
double e2;
double e3;
};
class Rec: public shape{
public:
Rec(double e)
{
this->e = e;
}
double getC(void)
{
return 4*e;
}
private:
double e;
};
double countC(shape *arr[], int n)
{
double sum = 0;
for(int i=0; i<n; i++)
{
sum += arr[i]->getC();
}
return sum;
}
int main()
{
shape x; //加这么一句,逻辑上说声明个图形对象(没交代是什么图形)
Cir c(1);
Rec r(3);
Cir c1(2);
Tri t(3,3,3);
shape *arr[] = {&c, &r, &c1, &t};
cout << "total C: "<<countC(arr, 4) << endl;
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ shape2.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
total C: 39.84
@ubuntu:/mnt/hgfs/ub2$
这种情况下声明了非法对象x却不会报错,而使用纯虚函数则可以对类shape进行限制,不能让其声明对象。
如下
#include <iostream>
using namespace std;
class shape{
public:
virtual double getC(void) = 0;
};
class Cir:public shape{
public:
Cir(double ri):r(ri) { }
double getC(void)
{
return 2*3.14*r;
}
private:
int r;
};
class Tri:public shape{
public:
Tri(double a, double b, double c):e1(a),e2(b),e3(c){ }
double getC(void)
{
return e1+e2+e3;
}
private:
double e1;
double e2;
double e3;
};
class Rec: public shape{
public:
Rec(double e)
{
this->e = e;
}
double getC(void)
{
return 4*e;
}
private:
double e;
};
double countC(shape *arr[], int n)
{
double sum = 0;
for(int i=0; i<n; i++)
{
sum += arr[i]->getC();
}
return sum;
}
int main()
{
shape x; //加这么一句,逻辑上说声明个图形对象(没交代是什么图形)
Cir c(1);
Rec r(3);
Cir c1(2);
Tri t(3,3,3);
shape *arr[] = {&c, &r, &c1, &t};
cout << "total C: "<<countC(arr, 4) << endl;
}
运行结果能够有报错
@ubuntu:/mnt/hgfs/ub2$ g++ shape3.cpp
shape3.cpp: In function ‘int main()’:
shape3.cpp:64:11: error: cannot declare variable ‘x’ to be of abstract type ‘shape’
shape x; //加这么一句,逻辑上说声明个图形对象(没交代是什么图形)
^
shape3.cpp:5:7: note: because the following virtual functions are pure within ‘shape’:
class shape{
^~~~~
shape3.cpp:7:17: note: virtual double shape::getC()
virtual double getC(void) = 0;
^~~~
@ubuntu:/mnt/hgfs/ub2$
回顾例26,有个结论:基类的指针可以指向派生类的对象,而派生类的指针是不能指向基类对象的
现在想了解派生类析构函数相关的内容,于是有
#include <iostream>
using namespace std;
class A{
public:
A(){ }
~A(){ cout<<"A~~~~~~~~~~~~"<<endl;}
};
class AX:public A{
public:
~AX(){ cout<<"AX~~~~~~~~~~~~"<<endl;}
};
int main()
{
AX a;
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ base1_7.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
AX~~~~~~~~~~~~
A~~~~~~~~~~~~
@ubuntu:/mnt/hgfs/ub2$
说明,派生类声明对象时,会先析构派生类,再析构基类。
接下来是基类指针指向派生类然后释放指针
#include <iostream>
using namespace std;
class A{
public:
A(){ }
~A(){ cout<<"A~~~~~~~~~~~~"<<endl;}
};
class AX:public A{
public:
~AX(){ cout<<"AX~~~~~~~~~~~~"<<endl;}
};
int main()
{
A *p = new AX;
delete p;
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ base1_8.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
A~~~~~~~~~~~~
@ubuntu:/mnt/hgfs/ub2$
发现这时候只会析构基类,如果想同时也能够析构派生类,则需要把基类析构函数声明成虚析构函数,如下
#include <iostream>
using namespace std;
class A{
public:
A(){ }
virtual ~A(){ cout<<"A~~~~~~~~~~~~"<<endl;}
};
class AX:public A{
public:
~AX(){ cout<<"AX~~~~~~~~~~~~"<<endl;}
};
int main()
{
A *p = new AX;
delete p;
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ base1_9.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
AX~~~~~~~~~~~~
A~~~~~~~~~~~~
@ubuntu:/mnt/hgfs/ub2$
在很多开发情景下是需要字符串和数字间的转换,因为有时需要把数值转成字符串显示在屏幕上,有时需要把显示的字符串转成数值进行数据处理。
先简单回顾一个转换函数
atoi()
这是字符串转数值的函数,可以执行shell指令的man查看具体用法
@ubuntu:/mnt/hgfs/ub2$ man atoi
先编个小例子
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
int data = atoi("1234");
cout<< data << endl;
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ atoi1.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
1234
@ubuntu:/mnt/hgfs/ub2$
假如说传的不是1234而是abcd
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
int data = atoi("abcd");
cout<< data << endl;
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ atoi2.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
0
@ubuntu:/mnt/hgfs/ub2$
说明输入字符串不是数的时候就会认定为非法(比如abcd),返回0。这里又存在一个问题:如果我转换的数是0的话,就不知道是非法情况还是没有非法。于是我们可以优化如下
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
int myatoi(const char *str)
{
if(*str<'0' || *str>'9')
printf("wrong arg!!\n");
else
return atoi(str);
}
int main()
{
int data = myatoi("abcd");
cout<< data << endl;
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ atoi3.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
wrong arg!!
12
@ubuntu:/mnt/hgfs/ub2$
发现打印信息有12,这个是执行**cout<< data << endl;**的结果,12表示printf打印内容的字符个数。
有人会说能不能在执行printf后直接跳转,类似如下
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
int myatoi(const char *str)
{
if(*str<'0' || *str>'9')
printf("wrong arg!!\n");
goto XXX;
else
return atoi(str);
}
int main()
{
int data = myatoi("abcd");
cout<< data << endl;
XXX:
cout << "XXX" << endl;
}
运行结果报错
@ubuntu:/mnt/hgfs/ub2$ g++ atoi4.cpp
atoi4.cpp: In function ‘int myatoi(const char*)’:
atoi4.cpp:12:5: error: ‘else’ without a previous ‘if’
else
^~~~
atoi4.cpp:11:11: error: label ‘XXX’ used but not defined
goto XXX;
^~~
@ubuntu:/mnt/hgfs/ub2$
这是因为goto不能跨函数使用。
而在C++中是有方法解决这个问题的,即异常处理,使用关键字throw、try、catch
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
int myatoi(const char *str)
{
if(*str<'0' || *str>'9')
throw "wrong arg!!";//抛给异常处理
else
return atoi(str);
}
int main()
{
try{
int data = myatoi("abcd");
cout<< data << endl;
}
catch(const char *p)//异常处理
{
cout << p << endl;
}
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ atoi5.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
wrong arg!!
@ubuntu:/mnt/hgfs/ub2$
在C++中已经对所抛的异常处理内容进行规范,纳在exception类中。
#include <exception>
对上例进行规范,优化如下
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <exception>
using namespace std;
class myexception:public exception{
public:
const char* what() const throw()
{
return "wrong arg!!";
}
};
int myatoi(const char *str)
{
if(*str<'0' || *str>'9')
throw myexception();
else
return atoi(str);
}
int main()
{
try{
int data = myatoi("asdfas");
cout<< data <<endl;
}
catch(myexception e)
{
cout<< e.what() <<endl;
}
}
运行结果
@ubuntu:/mnt/hgfs/ub2$ g++ atoi_exception.cpp
@ubuntu:/mnt/hgfs/ub2$ ./a.out
wrong arg!!
@ubuntu:/mnt/hgfs/ub2$
使用exception的好处在于它对各种常见异常进行了规范,我们只需使用相应的处理函数即可,效率得以提高。以下是预定义异常类各种标准异常。