关于程序员:番外篇深扒C函数

5次阅读

共计 5987 个字符,预计需要花费 15 分钟才能阅读完成。

函数指针

获取函数的地址:函数的名字就是地址

函数指针申明与应用:

// 函数原型
double pam(int);            

// 函数指针申明和赋值
double (*pf)(int);        // 函数指针申明
pf=pam;                // 函数指针赋值
double (*pf)(int)=pam;        // 函数指针申明和赋值
auto pf=pam;            // 应用 C ++11 的主动类型推断来申明和赋值

// 应用
pf(123);
(*pf)(123);            // 留神 (*pf) 的括号肯定要保留下来,不然意义就不同了
pam(123);
// 这三种模式等价, 之所以会这样是因为两种学派的了解不同;而我集体认为第一种用法比第二种用法正当;当前在应用时看到 (*pf)(123),你间接看成 pf(123) 即可,将 (*) 看成多余的就好

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

// 函数原型
double f1(int);
double f2(int);    
double f3(int);                

// 函数指针数组的申明和赋值
double (*pfa[3])(int);            // 函数指针数组的申明
pfa[0]=f1;                // 函数指针数组的赋值示例
double (*pfa[3])(int)={f1,f2,f3};    // 函数指针数组的申明和赋值
// 不能够应用 auto 来主动初始化,因为 auto 只能用于单值初始化, 而不能初始化列表
//ps:初始化 = 申明 + 赋值;初始化列表就是{}

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

// 诚实说,C++ 的指针申明挺简单,如果可能很好的了解上面的申明,就证实你对 C ++ 的申明一目了然了
const double *(*(*pd)[3])(const double *,int);
// 了解所有指针申明的要点是,从里向外去解读;留神 [] 的优先级比 * 高

---------------------------------------------------------------------------------------
// 要简化函数指针的申明过程,除了应用 auto 之外,还能够应用 typedef,例如:// 函数原型
double pam(int);

// 将函数指针类型弄成标识符模式
typedef double (*p_func)(int);

// 函数指针申明
p_func pf;

非动态类成员函数指针

与类的非静态数据成员变量不同,类的非动态成员函数不会在运行时为每个类的实例对象生成一份专有的机器语言代码,类的非动态成员函数在内存中只有一份,即类的非动态成员函数的地址也是惟一的。那么为什么不同的实例调用非动态成员函数的时能够拜访到属于实例自身的数据呢?这是因为非动态成员函数默认的第一个参数就是 this 指针,只是没有明写进去罢了

类的非动态成员函数默认的第一参数就是 this

受 this 指针的影响,非动态类成员函数指针 的申明和应用与 个别的函数指针(含动态成员函数指针)都不同

// 类的非动态成员函数指针 申明、赋值、应用 都跟一般状况不同
// 申明时须要增加类名
int (classA::*pFun)(int);        // 留神类名搁置的地位,类名是用来指明 this 指针的类型的
// 赋值时须要增加类名和 &
pFun=&classA::Fun;
// 应用时须要加上实例对象
(classAInstance->*pFun)(12345);        // 留神一个 '('、')','*' 都不能少,格局就这样死死固定住了;// 如果这个语句呈现在非动态成员函数中,不要认为 classAInstance 不写零碎会主动加上 this; 如果你是想调用 this 的 pFun 就本人手动加上,不然会报错,// 类的动态成员函数指针 申明、赋值、应用
// 申明时
int (*pFun)(int);
// 赋值时须要增加类名
pFun=classA::Fun;
// 应用时
pFun(12345);        // 或
(*pFun)(12345);

函数模板

函数模板将类型作为参数传递给函数模板,使编译器生成该类型的函数;又叫通用编程或参数化类型;

由函数模板对应的具体函数称为称为模板实例(instantiation)

隐式实例化、显式实例化、显式具体化统称为具体化(specialization)

类型参数不能有默认值,然而非类型参数能够有默认值

函数模板的申明格局

// T 叫做类型参数
template<typename T>            // 能够写成 template<class T>,然而这个是 C ++98 之前的写法;typename 这个关键字是 C ++98 定义的;函数模板参数列表中能够有多个参数,如 template<typename T1,typename T2>
void Swap(T &a,T &b);            // 返回类型能够不可也写成 T???

函数模板的定义格局

这只是一个函数模板,并不是函数;函数模板用于通知编译器如何定义函数;只有在编译时检测到代码中须要用到这个模板,编译器才会根据具体类型依照模板格局生成模板实例,这个过程称为隐式实例化(implicit instantiation)

编译完结之后机器码中并没有函数模板,只可能存在模板实例的机器码

模板函数的定义能够放在应用到函数模板的函数前面

template<typename T>            
void Swap(T &a,T &b)
{
    T temp;
    temp=a;
    a=b;
    b=temp;
}

模板函数的应用

// 应用时都不必指明类型,编译器会自动检测参数列表的数据类型来确定函数的类型
int main()
{
    int i=10;
    int j=20;
    
    Swap(i,j);        // 隐式实例化
    Swap<>(i,j);    // 不带参数的显式实例化,确保编译器应用函数模板的实例化版本

    double x=1.3;
    double y=3.6;

    Swap(x,y);        // 隐式实例化
    Swap<>(x,y);    // 不带参数的显式实例化,确保编译器应用函数模板的实例化版本

    // 疑难://Swap(i,x);    // 会怎么?return 0;
}

函数模板的重载

template<typename T>            
void Swap(T &a,T &b)
{
    T temp;
    temp=a;
    a=b;
    b=temp;
}

template<typename T>
void Swap(T *a,T *b,int n)
{
    T temep;
    for(int i=0;i<n;++i)
    {temp=a[i];
        a[i]=b[i];
        b[i]=temp;
    }
}

int main()
{
    int i=10;
    int j=20;
    
    Swap(i,j);

    int array1[3]={1,2,3};
    int array2[3]={4,5,6};

    Swap(array1.array2);

    return 0;
}

函数模板的局限性

template <typename T>
void f(T a,T b)
{
    //a=b;    // 本来是想做内容赋值操作的,但如果 T 类型是数组指针,那么最终只是 a 变成了 b 的指针,a 并没有取得 b 数组的内容;又或者以后 T 类型并没有 '=' 操作;
    //a>b;    // 本来是想做内容比拟操作的,但如果 T 类型是数组指针,那么最终只是比拟了 a 指针和 b 指针,并没有进行内容比拟;又或者以后 T 类型并没有 '>' 操作;
    // 当前留神 T 只解决非数组类型,T* 才是用来解决数组类型的;}

函数模板的显式具体化(explicit specialization)

所谓的显式具体化就是手动写一个函数模板的模板实例

函数模板显式具体化的申明格局:

// 函数模板
template <typename T>
void Swap(T &,T &);

// 函数模板的显式具体化,第一种写法
template <>
void Swap<job>(job &,job &);    // 留神参数列表肯定要和对应的模板雷同;// 函数模板的显式具体化,第二种写法
template <>
void Swap(job &,job &);            // 留神参数列表肯定要和对应的模板雷同;

如果一个名称 f 同时存在非模板函数、显式具体化函数、惯例模板,将遵循第三代函数应用优先程序(C++98 规范):非模板函数 > 显式具体化函数 > 惯例模板

实例化和具体化

  • 实例化和具体化是不同的概念:

    • 实例化是由编译器主动生成模板实例的过程
    • 具体化是手动写出一个模板实例的过程
    • 实例化 template 前面没有 <>
    • 具体化 template 前面有 <>

显式实例化 (explicit instantiation) 的办法

// 函数模板
template <typename T>
void Swap(T &,T &);

// 函数模板的显式实例化,第一种形式
template void Swap<int>(int,int);    // 编译器看到这条指令将马上主动生成 Swap 的 int 版本,不用等到调用;这样做有什么用?// 函数模板的显式实例化,第二种形式
... 主函数中
    int i=10;
    double x=1.3;
    Swap<double>(i,x);                // 生成 Swap 的 double 类型的实例以备调用,如果没有显式实例化的话,到这里会报错;因为没有方法隐式实例化;//ps:然而其实即便应用了显式化,这里因为 Swap 的参数是 double 援用类型,可是变量 i 是 int 类型;同样会报错,除非 Swap 函数参数列表改成 Swap(const T &,T &),[笑哭.jpg]这样有意思吗
...

ps:同一个文件中如果同时呈现同一类型函数的 显式实例化 和 显式具体化,将出错

编译器抉择应用哪个函数版本

重载解析(overloading resolution):面对函数重载、函数模板、函数模板重载时,C++ 决定应用哪一个函数的过程

具体解释这个策略须要将近一章的篇幅 = =,上面只讲大抵步骤

函数调用的步骤(假如函数调用为 may(‘B’))

  1. 先找出所有名称为 may 的函数
  2. 再从这些函数中找出能够只承受一个参数 ’B’ 的 may 函数(实参类型可强转为形参类型的也算)
  3. 再从这些函数中抉择一个最佳函数,优先级为:惯例函数齐全匹配 > 模板函数齐全匹配 > 晋升转换(例:char 和 short 主动转为 int,float 主动转为 double) > 规范转换(例:int 转换为 char,long 转换为 double) > 用户定义的转换

齐全匹配:实参类型与形参类型齐全等价,等价表如下所示:

ps:倒数第二列是不是错了???

  • 如果应用以上优先级抉择之后还是剩下一个以上的齐全匹配函数,那么将要应用以下规定持续筛选:

    • 非模板函数优先于模板函数
    • 非 const 的指针和援用的实参优先与非 const 的指针和援用的形参配对
    • 如果剩下的都是齐全匹配的模板函数,则应用函数模板的局部排序规定 (partial ordering rules)(C++98 个性,次要是解决指针与非指针的问题) 来抉择最具体的

如果应用以上规定抉择之后还是剩下一个以上的候选函数,那么编译器将报错;

例子:调用函数 map(‘B’);

编号 候选函数 第一步后果 第二部后果 优先级
1 void may(int); T T 4
2 void may(double,double); T F
3 float may(float,float = 3); T T 5
4 void may(char); T T 1
5 char may(const char ); T F
6 char may(const char &); T T 1
7 template< typename T > void may(const T &); T T 3
8 template< typename T > void may(T *); T F

ps:整数类型是不会隐式转换成指针的

以上只是介绍了单参函数调用的函数原型匹配问题,若是有多个参数,状况将非常复杂;这里不再介绍

模板函数的倒退

问题一

在 C ++98 规范下的函数模板存在着很多不完满的中央,有时候因为不晓得具体的类型是什么,导致无奈失常编写函数模板定义, 例:

template<typename T1,typename T2>
void ft(T1 x,T2 y)
{
    ...
    ?type? xpy = x + y;        // 这时不晓得 xpy 要定义为什么类型能力正确接管到后果
    ...
}

为了解决下面的问题,C++11 引入了一种新的申明数据类型形式, 应用关键字 decltype

decltype 是在运行时确定变量类型的

decltype 确定类型的形式,满足以下哪个模式就用哪个模式的断定办法

// 模式一:...
int x;
decltype{x} y;            // 断定办法:y 的类型将为 {} 中的 ** 数值 ** 类型;这里 y 的类型将会是 int

// 更多例子:int j = 3;
int &k = j;
int &n = j;
decltype(j+6) i1;        //i1 的类型为    int        
decltype(100L) i2;        //i2 的类型为    long    
decltype(k+n) i3;        //i3 的类型为 int
...
// 模式二:...
long indeed(int);        // 函数申明
decltype{indeed(3)} y;    // 断定办法:y 的类型将为 {} 中函数的返回值类型;这里 y 的类型将会是 long(书上写说 y 的类型是 int,错了吧?)// 留神:indeed(3)并不会真的被调用
...
// 模式三:...
double xx = 4.4;
decltype{(xx)} y;        // 断定办法:y 的类型将为 {} 中变量类型的援用;这里 y 的类型将会是 double &
                        // 留神:这里的括号不能省略且括号中必须是变量(左值)


// 这里引入一个和函数模板话题不沾边的小概念://xx = 98.6;
//(xx) = 98.6;
// 以上的两条表达式的作用是一样的,就是赋值,无差别
...

于是下面的问题就能够解决:

template<typename T1,typename T2>
void ft(T1 x,T2 y)
{
    ...
    decltype{x + y} xpy = x + y;    //xpy 的类型就是 x + y 失去的后果数值的类型
    ...

    // 如果要屡次应用下面这种类型,每次都要写这么长,会感觉很麻烦,那么能够应用关键字 typedef,例:typrdef decltype{x + y} xpytype;
    
}

问题二

然而即便有了 decltype 还是存在问题,有时函数返回类型不能确定,例:

template<typename T1,typename T2>
?type? gt(T1 x,T2 y)                    // 返回类型无奈确定,无奈将?type? 改为 decltype{x + y},因为那时 x 和 y 还没有申明
{
    '''
    return x + y;
}

为了解决下面的问题,C++11 引入了一个新语法后置返回类型,格局为:

auto h(int x,float y) -> double;
// 该函数的返回类型是 double,auto 只是占位用的

小弟才浅,如果本篇文章有任何谬误和倡议,欢送大家留言

感激大家的点赞、珍藏

微信搜「三年游戏人」第一工夫浏览最新内容,获取一份收集多年的书籍包 以及 优质工作内推

正文完
 0