《C++Primer 5e》学习笔记(5):函数

1.函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用的函数。此时,主调函数的执行被暂时中断,被调函数开始执行。

2.函数定义规定了实参与形参存在的对应关系,但是并没有规定实参的求值顺序。编译器能以任意可行的顺序对形参进行求值。

3.函数的返回值不能是数组类型或函数类型,但可以是指向数组或函数的指针。

4.局部静态对象在程序执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。

#include <iostream>
using namespace std;
size_t count_calls()
{
    static size_t ctr=0; //调用结束后,这个值仍然有效
    return ctr++;
}
int main()
{
    for(size_t i=0;i!=10;++i)
        cout<<count_calls()<<endl; //依此输出0..9
    cout<<ctr<<endl; //编译错误,ctr没有被声明
    return 0;
}


5.函数可以只有声明没有定义。在声明中可以省略形参的名字,只需有其类型。

6.C++语言支持所谓的分离式编译,其允许我们把程序分割到几个文件中去,每个文件独立编译。

7.和其他变量一样,形参的类型决定了形参和实参交互的方式。如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝给形参。PS:建议尽量多用引用避免使用拷贝。拷贝大的类类型对象或者容器对象时比较低效,甚至有的类类型根本就不支持拷贝操作 ,此时只能通过引用形参访问该类型的对象。对于不改变实参内容的引用,我们可以把形参定义成对常量的引用:

bool Is_Shorter(const string &s1,const string &s2)
{//比较两个string对象的长度
    return s1.size()<s2.size();
}


8.指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。因为指针使我们可以间接访问它所指的对象,所以通过指针可以修改它所指对象的值:

int n=0,i=42;
int *p=&n,*q=&i; //p指向n,q指向i
*p=42; //n的值现在改变
p=q; //p现在指向了i,但i和n的值都不变

//指针的行为与之类似
void reset(int *ip)
{
    *ip=0; //改变指针ip所指对象的值
    ip=0; //只改变了ip的局部拷贝,实参未被改变
}


9.const形参和实参:(顶层const作用于对象本身)和其他初始化过程一样,当用实参初始化形参时会忽略掉顶层const。换句话说,形参的顶层const被忽略掉了。当形参有顶层const时,传给它常量或非常量都是可以的:

void fcn(const int i) //fcn能够读取i,但不能向i写值
void fcn(int i) //错误,重复定义了fcn


另一方面,如果形参是某种类型的指针或者引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const的底层的:

void loopup(account&); //函数作用于account的引用
void loopup(const account&); //新函数,函数作用于常量引用


10.我们可以使用非常量初始化一个底层const,但是反过来不行;同时一个普通的引用必须用同类型的对象初始化。

#include <iostream>
using namespace std;

void reset(int *ip)
{
    *ip=0;
    ip=0;
}
void reset(int &i)
{
    i=0;
}
int main()
{
    int i=42;
    const int ci=i;
    string::size_type ctr=0;
    reset(&i); //调用形参类型是int*的reset函数
    //reset(&ci); //错误:不能用指向const int对象的指针初始化int*
    reset(i); //调用形参类型是int&的reset函数
    //reset(ci); //错误:不能把普通引用绑定到const对象ci上
    //reset(42); //错误:不能把普通值绑定到字面值上
    //reset(i+i); //错误:不能使用求值结果为int的表达式
    //reset(ctr); //错误:类型不匹配,ctr是无符号类型

    return 0;
}



要想调用引用版本的reset,只能用int类型的对象,而不能使用字面值、求值结果为int的表达式、需要转换的对象或者const int的对象。类似的,想要调用指针版本的reset只能用int*。

11.数组的引用:(注意引用运算符的优先级低于下标运算符)

#include <iostream>
using namespace std;

//void exa(int &a[5]) //将a声明成了引用的数组:编译错误
void exa(int (&a)[5]) //对具有5个整数的整型数组的引用
{
    for(int i=0;i<5;i++)
        a[i]+=1;
}
int main()
{
    int a[5]={1,2,3,4,5};
    exa(a);
    for(int i=0;i<5;i++)
        cout<<a[i]<<endl; //输出2..6
    return 0;
}


12.

int main(int argc,char *argv[]){/*..*/} //........(1)
int main(int argc,char **argv){/*..*/} //........(2)


式(1)中第二个形参argv是一个数组,它的元素是指向C风格字符串的指针;第一个参数argv表示数组中字符串的数量。由于第二个形参是数组,所以main函数也可以定义成式(2)这样。

当实参传递给main函数之后,argv的第一个元素指向程序的名字或者一个空字符串,接下来的元素以此传递给命令行提供的实参,最后一个指针的元素保证为0(即空字符串)。

13.return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。返回一个值的方式和初始化变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。函数返回的结果(即函数的返回值)将被拷贝到调用点。如果函数返回引用,则该引用仅是它所引对象的一个别名:

const string& Shorter_String(const string &s1,const string &s2)
{
    return s1.size()<=s2.size()?s1:s2;
}


其中形参和返回类型都是const string的引用,不管是调用函数还是返回结果都不会真正拷贝string对象。

14.函数完成后,它所占的存储空间也随之被释放掉,因此,函数终止意味着局部变量的引用将指向不再有效区的内存区域。

15.函数的返回类型决定函数调用是否为左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。

16.C++11新标准规定,函数可以返回花括号包围的值的列表:

vector<string> process()
{
    //...
    //expected和actual是string对象
    if(expected.empty())
        return {}; //返回一个空vector对象
    else if(expected==actual)
        return {"functionX","okay"}; //返回列表初始化的vector对象
    else return {"functionX",expected,actual};
}


17.因为数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或引用:

typedef int arr[10]; //arr是一个类型别名,表示的类型是含有10个整数的数组
using arr=int[10]; //arr的等价声明
arr* func(int i); //func返回一个指向含有10个整数的数组的指针

//如果不用类型别名,我们必须牢记被定义的名字后面数组的维度
int arr[10]; //arr是一个含有10个整数的数组
int *p1[10]; //p1是一个含有10个指针的数组
int (*p2)[10]=&arr; //p2是一个指针,指向含有10个整数的数组


和这些声明一样,如果我们定义一个返回数组指针的函数,则数组的维度必须跟在函数名字之后:

int (*func(int i))[10]; //如果没有最外面的括号,函数的返回类型将是指针的数组


18.C++中,名字查找发生在类型检查之前

string read();
void print(const string&);
void print(double); //重载print函数
void example(int ival)
{
    bool read=false; //新作用域:隐藏了外层的read
    string s=rread(); //错误:read是一个bool值,而非函数
    void print(int ); //新作用域:隐藏了之前的print
    printf("Hello LHS!"); //错误:print(const string&)被隐藏了
    print(ival); //正确:当前print(int)可见
    print(3.14); //正确:调用print(int),print(double)被隐藏
}


当我们调用print函数时,编译器首先寻找对函数的声明,一旦在当前作用域找到了所需的名字,编译器就会忽略掉外层作用域的名字,剩下的工作就是检查函数调用是否有效了。

19.调用含有默认实参的函数

string func(int a=24,int b=45,char bk=' ');
string tmp;
tmp=func(); //等价于func(24,45,' ')
tmp=func(66); //等价于func(66,45,' ')


用作默认实参的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时。

20.函数指针指向的函数而非对象。和其他指针一样,函数指针指向某种类型,其类型由它的返回类型和形参类型共同决定。

//比较两个string对象的长度
bool Length_Compare(const string&,const string&);


该函数的类型是bool(const string&,const string&),想要声明一个可以指向该函数的指针,只需用指针替换函数名即可:

//pf指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
bool (*pf)(const string&,const string&); 


21.使用函数指针:

当我们把函数名作为一个值使用时,该函数自动地转换成指针:

pf=Length_Compare; //pf指向名为Length_Compare的函数
pf=&Length_Compare; //等价的赋值语句:取地址符是可选的


此外,我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针:

bool p1=pf("hello","goodbye"); //调用Length_Compare函数
bool p2=(*pf)("hello","goodbye"); //一个等价的调用
bool p3=Length_Compare("hello","goodbye"); //另一个等价调用




猜你喜欢

转载自blog.csdn.net/AC_Gibson/article/details/50469650
今日推荐