第7章、函数——C++的编程模块

1、函数基本知识

要使用C++函数,必须完成以下工作:
*通过函数定义
*提供函数原型
*调用函数

#include <iostream>

using namespace std;
void simple() //函数原型
int main()
{
    simple();
    cout << "Hello World!" << endl;
    return 0;
}
void simple(){//函数定义
    cout<<"simple\n";//
}


函数有两类:有返回值和无返回值的函数。
对于有返回值的函数,必须使用返回语句,以便于将值返回给调用函数。C++的返回值类型有限制:不可以是数组,可以是整型、浮点型、指针、结构体和对象(数组可以作为结构体或者对象的一部分返回)。

为什么需要原型
原型描述了函数到编译器的接口。也就是,它将函数返回值的类型以及参数类型和数量告诉编译器。

避免使用函数原型的唯一方法是:在第一次使用函数之前定义它,但是效率不高。

函数原型不要求提供变量名,有参数列表就足够了。

void cheers(int);

原型中的变量名相当于占位符,可以和函数定义的变量名不一样。

为了避免出错,函数原型确保以下几点:
编译器正确处理函数返回值
编译器检查使用的参数数目是否正确
编码器检查使用的参数类型是否正确,如果不正确,则转换成正确类型(如果可以的话)

原型自动将被传递的参数强制转换为期望的类型。在编译阶段进行的原型化被称为静态类型检查。静态类型检查可以捕获许多在运行阶段非常难以捕获的错误。

将数组作为参数意味着什么?

传递常规变量时,函数将使用该变量的拷贝;但是,传递数组时,函数将使用原来的数组。这种区别并不违反C++按值传递的方法,函数
int sum_arr(int arr[ ],ini n)仍然传递一个值,这个值是一个地址,而不是数组的内容。数组名与指针对应是一个好事,将数组地址作为参数可以节省复制数组所需的时间和内存;另外一方面,使用原始数组增加了破坏数据的风险,const限定符可以解决这种问题。

使用数组区间的函数

方法一:数组的起始处的指针作为一个参数,数组长度作为第二个参数。
方法二:传递两个指针作为参数,一个指针指向标识数组的开头,一个指向数组的尾部,

尽可能使用const
将指针参数声明为指向常量数据的指针有两个理由
1、这样可以避免由于无意间修改数据而导致的编程错误;
2、使用const使得函数能够处理const和非const实参,否则只能接受非const数据

三种const

1、指向const int的指针( a pointer to const int)

int grop=16;
int chip=12;
const int * p_s=&grop;

*p_s =20;
错误:禁止修改p_s指向的值
p_s=&chip;
p_s可以指向另一个变量

2、指向Int的常量指针(a const pointer to int)

int grop=16;
int chip=12;
int * const  p_s=&grop;

*p_s =20;
正确:p_s可以用来修改值
p_s=&chip;
错误:禁止修改p_s指向的变量

3、指向const对象的const指针:

double trouble=2.0E30;
const double *const stick=&trouble;

stick只能指向trouble,而stick不能用来修改trouble的值,stick和stick*都是const。

C++禁止将非const的地址赋给const指针,如果非要这么做,可以使用const_cast强制类型转换。

函数指针

假设要设计一个名为estimate()的函数,估算编写指定行数的代码所需的时间,并且希望不同的程序员都将使用该函数。
对于所有的用户来说,estimate()中的一部分代码都是相同的,但该函数允许每个程序员提供自己的算法来估算时间。为了实现这个目标,采用的机制是,将程序员要使用的算法函数的地址传递给estimate()。为此,必须完成下面的工作:
1、获取函数的地址
2、声明一个函数指针
3、使用函数指针来调用函数

1、获取函数的地址

获取函数地址就是:只要使用函数名(后面不跟参数)即可。
process(think);//process函数内部能够调用think()函数;
thought(think());//thought函数首先调用think()函数,然后将think()函数返回值传递给thought()函数。

2、声明一个函数指针

声明指向某种数据类型的指针时,必须指定指针指向的类型。同样,声明指向函数的指针时,也必须指定指针指向的函数类型。

double pam(int);//函数原型
double (*pf)(int);//正确的指针类型声明

由于pam是函数,因此(*pf)也是函数。而如果(*pf)是函数,则pf就是函数指针。
下面两种:

double (*pf)(int);//一个指向函数的指针 也就是函数指针
double *pf(int);//一个返回double *类型指针的函数
//正确的函数指针使用方法
double pam(int);//函数原型
double (*pf)(int);//正确的指针类型声明
pf=pam;//pf 现在指向pam函数
注意,pam()的特征标和返回类型必须与pf相同。如果不相同,编译器将拒绝这种赋值。
double ned(double);
int ted(int);
double (*pf)(int);
pf=ned;//不可用,特征标不匹配(输入参数类型)

pf=ted;//不可用,返回类型不匹配

现在回头看estimate()函数,假设将算法函数(假如为pam()函数)的地址传递给它,则其原型将如下:

void estimate(int lines,double(*pf)(int));
//第二个参数是一个函数指针,它指向的函数接受一个int参数,并返回一个double值。
estimate(50,pam);//用法

3、使用函数指针来调用函数

下面是使用指针调用被指向的函数。

double pam(int);
double (*pf)(int);
pf=pam;//pf指向pam函数
double x=pam(4);//以下两个一样
double y=(*pf)(5);

实际上,C++也允许像函数名那样使用pf:

double y=pf(5);

深入探讨函数指针

下面这些函数的特征标和返回类型相同

const double * f1(const double ar[],int n);
const double * f2(const double [],int n);
const double * f3(const double *,int n);

const double ar[]可以简化为const double [],const double ar*可以简化为const double *,const double ar[]和const double ar * 的含义相同

以下为使用案例

#include <iostream>

using namespace std;
const double * f1(const double ar[],int n);
const double * f2(const double [],int n);
const double * f3(const double *,int n);
int main()
{

    double av[3]={111,222.2,333.3};
    const double *(*p1)(const double *,int)=f1;//函数指针
    auto p2=p1;//自动类型推导
//    const double *(*p2)(const double *,int)=f2;

    cout<<(*p1)(av,3)<<":"<<*(*p1)(av,3)<<endl;//(*p1)(av,3)函数返回值的地址,*(*p1)(av,3)返回值
    cout<<p2(av,3)<<":"<<*p2(av,3)<<endl;//p2(av,3)函数返回值的地址

    const double *(*pa[3])(const double *,int)={f1,f2,f3};//*pa[3]表明pa是一个包含三个指针的数组

    auto pb=pa;//
    /*
     * pa和&pa的差别:pa是数组第一个元素的地址,即是&pa[0],他是单个指针的地址;
     * &pa是整个数组(三个指针块的)地址。
     * 数字上说,pa与&pa的值相同,但它们类型不一样。
     *第一个差别: pa+1为数组中下一个元素的地址,而&pa+1为数组pa后一个12字节内存块(这里假定地址为4字节)。
     * 第二个差别:要想得到第一个元素的值,只需要对pa解除一次引用,但需要对&pa解除两次引用,如下
     *   **&pa==*pa == pa[0]
     * 
*/

    for(int i=0;i<3;i++){// pa[i](av,3) 返回值地址
        cout<<pa[i](av,3)<<" pa[i]:"<<*pa[i](av,3)<<endl;
    }

    for(int i=0;i<3;i++){
        cout<<pb[i](av,3)<<"  pb[i]:"<<*pb[i](av,3)<<endl;
    }
    auto pc=&pa;//增加了一层间接,因此需要在某个地方加*
        //(*pc)[0](av,3) 返回一个 const double *
    cout<<(*pc)[0](av,3)<<"(*pc)[0](av,3) :"<<*(*pc)[0](av,3)<<endl;

    const double *(*(*pd)[3])(const double *,int)=&pa;
     cout<<(*pd)[0](av,3)<<"(*pd)[0](av,3) :"<<*(*pd)[0](av,3)<<endl;

    const double *pdb=(*pd)[1](av,3);

    cout<<pdb<<":"<<*pdb<<endl;//pdb是函数 返回 const double *
    cout<<(*(*pd)[2])(av,3)<<":"<<*(*(*pd)[2])(av,3)<<endl;
    return 0;
}

const double * f1(const double *ar,int n){
    return ar;
}
const double * f2(const double ar[],int n){
    return ar+1;
}
const double * f3(const double ar[],int n){
    return ar+2;
}

运行结果

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值