C++之高级编程20170914

/******************************************************************************************************************/

一、C++高级编程_抽象类_概念

前面有virtual 后面=0,则为纯虚函数,一个类里面有纯虚函数就变成了抽象类:

class Human {

private:

         int a;

public:

         virtual void eating(void) = 0;//纯虚函数

         virtual void wearing(void) = 0;//纯虚函数不需要定义

         virtual void driving(void) = 0;

};

抽象类不能用来实例化对象,若子类没有覆写所有的纯虚函数,则子类还是抽象类,即其实现的子类必须全部实现纯虚函数才可以实例化对象

成员函数都是纯虚函数则称为接口,接口是特殊的抽象类

作用:向下(派生类)定义好框架,向上提供统一的接口

(与java一致)

/******************************************************************************************************************/

二、C++高级编程_抽象类界面

一个程序由多个人编写,分为:

应用编程:使用类(使用类生成的动态库)

类编程:提供类,比如Englishman, Chinese

1.这样修改了类,只是动态库修改了,应用程序不用修改,实现了分层

使用动态库:

Makefile 书写:

Human: main.o libHuman.so

         g++ -o $@ $< -L./ -lHuman

#-L./  在当前目录下来查找动态库

#-lHuman 把Human动态库编译(连接)进去

%.o : %.cpp

         g++ -fPIC -c -o $@ $<

#编译时加上-fPIC  PIC是位置无关码

libHuman.so : Englishman.o Chinese.o Human.o

         g++ -shared -o $@ $^

#-shared生成动态库

执行时:

执行时,要指定库路径,不然出错

设置LD_LIBRARY_PATH 表示运行时去哪里找需要的库

例:LD_LIBRARY_PATH=./ 

./Human

LD_LIBRARY_PATH等于当前目录,然后执行程序

2.这样修改了类,只是动态库需要修改(make libHuman.so来需改动态库),应用程序不用修改,实现了分层。

但是如果修改了应用程序依赖的头文件,那么就需要修改应用程序,也就是需要重新编译应用程序,那么如何实现依赖的头文件不变呢,就需要相对固定的头文件,也就是头文件是相对固定的抽象类(接口),同时对于需要的其他依赖可以使用函数声明放到该头文件(或称抽象类的界面)

             app

---------------------------------

             Human.h

---------------------------------

Englishman                Chinese

3.对于基类的析构函数不应该使用纯虚函数,因为这样会使得其派生类还要再实现基类的析构函数

Human& e = CreateEnglishman("Bill", 10, "sfwqerfsdfas");

         Human& c = CreateChinese("zhangsan", 11, "beijing");

         Human* h[2] = {&e, &c};

         int i;

         for (i = 0; i < 2; i++)

                   test_eating(h[i]);

         delete &e;//虽然转型了,但是原本还是派生类Englishman对象,所以还会调用派生类Englishman的析构函数

         delete &c;

/******************************************************************************************************************/

三、C++高级编程_函数模板_引入

把变量类型作为参数来定义一个函数,就称为函数模版

定义:

template<类型参数表>

返回值 函数名(数据参数表)

{

         函数模板定义体;

}

例:

template<typename T>

//T表示变量类型,即T等价于类型

T& mymax(T& a, T& b)

//模版要求a ,b两个参数类型相同

{

         cout<<__PRETTY_FUNCTION__<<endl;//打印出函数的具体名称      

         return (a < b)? b : a;

}

int main(int argc, char **argv)

{

         int ia = 1, ib = 2;

         float fa = 1, fb = 2;

         double da = 1, db = 2;

        

         mymax(ia, ib);//生成不同的函数

mymax(fa, fb);//通过函数模版得到具体函数的过程称为模版的实例化或模版的具体化

mymax(da, db);//得到是三个不同的函数,三个调用对应的是不同的函数

return 0;

}

1. 函数模板只是编译指令,一般写在头文件中;

2. 编译程序时,编译器根据函数的参数来“推导”模板的参数;然后生成具体的模板函数

1).示例代码:

int a; int b; mymax(a, b);

编译器根据函数参数a,b推导出模板参数为int,所以把模板中的T绑定为int;

编译程序时生成如下函数:

int& mymax(int& a, int& b)

{

         return (a<b)?b:a;

}

2).参数推导过程:

首先要苛刻的类型匹配(与函数模版匹配,是否满足函数模版要求),如果无法苛刻类型匹配,那么参数要进行有限的类型转换,如果进行有限的类型转换后还是不能匹配,那么就会出错。

3.有限的类型转换:

1).函数模版只支持2中隐式类型转换:

I、const转换:函数传参为非const引用/指针,它可以隐式转换为const引用/指针,但是反过来传参是const形参是非const就不行。

II、数组或函数指针的转换:

传参为数组(形参为指针)可以隐式转换为“指向第一个元素的指针”(当形参为普通元素类型则数组转换为数组最后一个元素)

传参为“函数的名字”时,它隐式转换为“函数指针”

int main(int argc, char **argv)

{

         char a[]="ab";

         char b[]="cd";

        

         mymax(a, b);  /* T=char[3] //T推导为char[3]*/

         mymax2(a, b);

         char a2[]="abc";

         char b2[]="cd";

         //mymax(a2, b2);  /* mymax(char[4], char[3]), 无法推导出T: mymax(char& [4], char& [3]), 因为两个参数类型不一样 //转换后类型不一样,所以有错*/

         mymax2(a2, b2);   /* mymax2(char[4], char[3]), 推导: mymax2(const char *, const char *); //T等价于char *,即都转换为“指向第一个元素的指针”,所以一致也就没错*/

         test_func(f1);//传参是函数名字和传参是函数名字取址是一样的,因为可以转换       

test_func(&f1);

return 0;

}

2).其他隐式转换都不支持,比如:算术转换,派生类对象向上转换。

3).参数类型为传值时,忽略实参的const,volatile等属性,因为传值时,会临时生成一个变量,此变量可读可写

/******************************************************************************************************************/

四、C++高级编程_函数模板_重载

函数模板的重载:

1.函数选择过程:

1).先列出候选函数,包括普通函数、参数推导成功的模板函数

2).这些候选函数,根据“类型转换”来排序(转换越小甚至没转换排前面更优先,注意:模板函数只支持有限的类型转换):

3).如果某个候选函数的参数,跟调用时传入的参数更匹配,则选择此候选函数

4).如果这些候选函数的参数匹配度相同

I、如果有一个非模板函数(普通函数),则选择它(优先选择普通函数)

II、如果只有模板函数,则选择“更特化”的模板函数(“更特化”:参数匹配更特殊,更具体,更细化)

例:

const T& mymax(T& a, T& b)//可以准换普通类型,也可以转换指针类型

const T mymax(T * a, T* b)//只能转换指针,即参数只能是指针,所以这个更细化,即更特化

III、否则,最后导致“二义性”错误(ambiguous)

例:

int *p1=&ia;

         int *p2=&ib;

         cout<<mymax(p1, p2)<<endl;//普通函数无法把指针转换为引用,函数模版可以,所以使用2即函数模版(当然如果有函数模版是指针形参的当然选择那个函数模版):

//输出使用结果表示使用的是函数模版:template<typename T>

const T& mymax(T& a, T& b)

{//推导中,引用只是别名而已,所以没有任何关系,即没有任何影响

                  cout<<"2: "<<__PRETTY_FUNCTION__<<endl;

                  return (a < b)? b : a;

}

注意,指针表示的解读:

从右往左读,遇到p就替换成"*p is a"遇到*就替换成"point to"

例:

const int *p //p指向只读的int

引用也是一样的,例:

int a=1;

const int &b=a;//定义了常量引用b,通过b无法修改a

再比如:

int *q=&a;

const (int *)p=q;//无法通过p修改q,但是p所指内容可修改,即q可修改

//const 修饰的是int *

好文:理解模板参数推导,可以看下文:

http://blog.csdn.net/coolmeme/article/details/43986163

要深入: C++标准之(ravalue reference) 右值引用介绍

http://www.cnblogs.com/opangle/archive/2012/11/19/2777131.html

/******************************************************************************************************************/

五、C++高级编程_类模板

1. 声明(一般写在头文件中)

template<typeclass T>

class AAA {

private:

         T obj; /* 使用T表示某种类型*/

public:

         void test_func(T& t);

         .....

};

2. 定义

template<typeclass T>

void AAA<T>::test_func(T& t) { .... }

3.使用类模板

1).用到时才生成代码(具体代码)

用到时再实例化:

AAA<int> a;

AAA<double> b;

2).一次性事先生成好代码

事先实例化:

template AAA<int>;

再使用:

AAA<int> a;

4.定做(类似重载)

1). 声明

template<>

class AAA<int> {

......

public:

         void test(void);

};

2). 定义

void AAA<int>::test(void) {...}

5.链表操作,以及容器中大量使用到了类模版

/******************************************************************************************************************/

六、C++高级编程_异常

多层次函数调用时,当最深层次的函数出错时,最上层的函数想知道错误,就必须一级一级来判断返回值,这样就太麻烦了,如何解决这个问题?

c语言中使用长跳转来解决这个问题。

在c++中,使用异常来处理:

当A调用B,B调用C时,函数A捕捉函数c发出的异常时:

谁捕捉异常?A

谁抛出异常?C

捕捉到异常后怎么处理?随A

1.抛出异常:

B()

{

         throw 某个对象:

}

2.捕捉处理:

A()

{

         try{

                   B();

         } catch(类形 对象)

         {

                   //处理

         }

}

一旦发生异常就会中断当前的执行,直接调到catch中

3.多种类型的异常,可以有多种捕捉,但是太多异常的话,就麻烦了,此时使用省略号来通配,

4.由于catch需要事先知道哪些类型,就使用声明,声明可能扔出哪些异常(声明里要包含内部可能出现的所有类型的异常,不包含的异常属于意料之外的异常,即使有捕捉处理但发生时还是捉不到,异常会继续向上抛出最终到系统默认处理(执行两个函数)),

5.即对于意料之外的异常,会执行2个函数:

"unexpected"函数(可以自己提供),

"terminate"函数(可以自己提供)

set_unexpected (my_unexpected_func);//设置自己提供的unexpected函数

set_terminate(my_terminate_func);//设置自己提供的erminate函数

"unexpected"函数用来处理声明之外的异常

"terminate"函数用来处理"catch分支未捉到异常"//catch分支未捉到异常最终会调用到terminate函数

也就是说,系统默认处理异常函数,可以由自己设置,但最终还是会调用系统终止函数,但系统终止函数也可以自己设置,但是最终程序还是会崩溃退出。

例:

class MyException

{

public:

         virtual void what(void)

{

         cout<<"This is MyException"<<endl;

}

};

class MySubException : public MyException

{

public:

         void what(void)

{

cout<<"This is MySubException"<<endl;

}

};

void C(int i) throw(int, double)//声明可能扔出哪些异常,这里没包含全

{

         int a = 1;

         double b= 1.2;

         float c = 1.3;

         if (i == 0)

         {

                   cout<<"In C, it is OK"<<endl;

         }

         else if (i == 1)

                   throw a;

         else if (i == 2)

                   throw b;

         else if (i == 3)

                   throw c;

         else if (i == 4)

                   throw MyException();

         else if (i == 5)

                   throw MySubException();

}

void B(int i)

{

         cout<<"call C ..."<<endl;

         C(i);

         cout<<"After call C"<<endl;

}

void A(int i)

{

         try {

                   B(i);

         } catch (int j)

         {

                   cout<<"catch int exception "<<j<<endl;

         } catch (double d)

         {

                   cout<<"catch double exception "<<d<<endl;

         } catch (MyException &e)//可以处理其子类异常(向上转换,隐式转换)  

{

                   e.what();//由于是虚函数,所以调用的还是派生类的      

} catch (...)

{//多种类型的异常,可以有多种捕捉,但是太多异常的话,就麻烦了,此时使用省略号来通配                

cout<<"catch other exception "<<endl;

         }

}

void my_unexpected_func()//自己提供的unexpected函数

{

         cout<<"my_unexpected_func"<<endl;

}

void my_terminate_func ()//自己提供的erminate函数

{

cout<<"my_terminate_func"<<endl;

int main(int argc, char **argv)

{

         int i;

         set_unexpected (my_unexpected_func);//设置自己提供的unexpected函数

         set_terminate(my_terminate_func);//设置自己提供的erminate函数    

         if (argc != 2)

{

                   cout<<"Usage: "<<endl;

                  cout<<argv[0]<<" <0|1|2|3>"<<endl;

                   return -1;

         }

         i = strtoul(argv[1], NULL, 0);

         A(i);

         return 0;

}

原文地址:https://www.cnblogs.com/yuweifeng/p/7522404.html