C++ 笔记2

第5~8章 函数

GitHub

建议下载下来用Typora软件阅读markdown文件

一、函数

函数—C++的编程模块(要提高编程效率,可更深入地学习STL和BOOST C++提供的功能

  • 1.提供函数定义 function definition
  • 2.提供函数原型 function prototype
  • 3.调用函数 function call
1
2
3
4
5
Void functionName(parameterlist)
{
statement(s)
teturn;
}
  • parameterlist:指定了传递给函数的参数类型和数量
  • void:没有返回值,对于有返回值的函数,必须有返回语句return
  • 1.返回值类型有一定的限制:不能是数组,但可以是其他任何类型—整数,浮点数,指针,甚至可以是结构和对象。
  • 2.函数通过将返回值复制到指定的CPU寄存器内存单元中来将其返回。

1.为什么需要原型

原型描述了函数到编译器的接口:

它将1.函数返回值类型(如果有的话)

以及2.参数的类型

和3.数量告诉编译器。(在原型的参数列表中,可以包含变量名,也可以不包含。原型中的变量名相当于占位符,因此不必与函数中的变量名相同)

  • 确保:编译器正确处理1,编译器检查2,3

2.函数参数传递和按值传递

  • 用于接收传递值的变量被称为形参(parameter),传递给函数的值被称为实参(argument)。
  • 值传递:调用函数时,使用的是实参的副本,而不是原来的数据。
  • 在函数中声明的变量(局部变量(自动变量))(包括参数)是该函数私有的,函数调用时:计算机将为这些变量分配内存;函数结束时:计算机将释放这些变量使用的内存。

3.函数和数组

1
2
3
int sum_arr(int arr[],int n);//arr=arrayname.n=size
int sum_arr(int * arr,int n);//arr=arrayname.n=size
//两者是等价的

a.数组与指针

  • 在C++中,当且仅当用于函数头或者函数原型中,int * arrint arr[]的含义才是相同的。它们意味着arr是一个int指针。
  • 然而,数组表示法(int arr[])提醒用户,arr不仅指向int,还指向int数组的第一个int。

b.const保护数组(输入数组原数据不能改变)

void show_array(const double ar[],int n);//声明形参时使用const关键字

  • 该声明表明,指针or指向的是常量数据。这意味着不能使用or修改数据。这并不意味着原始数据必须是常量,只意味着不能使用该指针来修改原始数据。
  • 如果该函数要修改数组的值,声明ar时不能使用const

c.如何使用数组作为参数传递给函数

对于处理数组的C++函数,必须将数组中的

1.数据类型

2.数组的起始位置

3.和数组元素中的数量提交给他

  1. 传统的C/C++方法是,将指向数组起始处的指针作为一个参数,将数组长度作为第二个参数(指针指出数组位置和数据类型)
  1. 第二种方法:指定元素区间(range)
    通过传递两个指针来完成:一个指针表示数组的开头,另外一个指针表示数组的尾部。例子:
1
2
3
4
5
6
7
8
9
10
int sum_arr(const int *begun,const int *end)
{
const int *pt;
int total=0;
for(pt=begin;pt!=end;pt++)
total=toatl+*pt;
return total;
}
int cookies[ArSize]= {1,2,4,8,16,32,64,128};
int sum=sum_arr(cookies,cookies+ArSize);

4.函数与C风格字符串

假设要将字符串(实际传递的是字符串第一字符的地址)作为参数传递给函数,则表示字符串的方式有三种:

  • 1.char数组T
  • 2.用引号的字符串常量
  • 3.被设置为字符串的地址的char指针。

5.函数和结构

涉及函数时,结构变量的行为更接近基于基本的单值变量

  • 1.按值传递–>如果结构非常大,则复制结构将增加内存要求,且使用的是原始变量的副本
  • 2.传递结构的地址,然后使用指针来访问结构的内容
1
2
3
4
5
6
7
rect rplace;
polar pplace;
void rect_to_polar(const rect*pxy,polar*pda)
{
...
}
rect_to_polar(&rplace,&pplace);

调用函数时,将结构的地址(&pplace)而不是结构本身(pplace)传递给它;将形参声明为指向polar的指针,即polar*类型。由于函数不应该修改结构,因此使用了const修饰符,由于形参是指针不是结构,因此应使用间接成员运算符(->),而不是成员运算符(.)。

  • 3.按引传递用,传指针和传引用效率都高,一般主张是引用传递代码逻辑更加紧凑清晰。

递归—C++函数有一种有趣的特点–可以调用自己(除了main())

1.包含一个递归调用的递归

1
2
3
4
5
6
7
void recurs(argumentlist)
{
statement1
if(test)
recurs(arguments)
statement2
}

如果调用5次recurs就会运行5次statement1,运行1次statement2.

2.包含多个递归调用的递归

1
2
3
4
5
6
7
8
void recurs(argumentlist)
{
if(test)
return;
statement;
recurs(argumentlist1);
recurs(argumentlist2);
}

3.从1加到n

1
2
3
4
5
6
7
8
9
10
class Solution
{
public:
int Sum_Solution(int n){
int ans=n;
ans&&(ans+=Sum_Solution(n-1));
return ans;
}
};
//&&就是逻辑与,逻辑与有个短路特点,前面为假,后面不计算。

6.函数指针

函数也有地址—存储其机器语言代码的内存的开始地址

    1. 获取函数的地址,只要使用函数名(后面不跟参数)即可。

      例如think()是个函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      process(think);//传递的是地址
      thought(think());//传递的是函数返回值
      //使用
      double pam(int);//原始函数声明
      double (*pf)(int);//函数指针声明
      pf=pam;//使用指针指向pam函数

      double x=pam(4);//使用函数名调用pam()
      double y=(*pf)(5);//使用指针调用pam()
      //也可以这样使用函数指针
      double y=pf(5);
    1. 进阶
      下面函数原型的特征表和返回类型相同
      1
      2
      3
      4
      5
      6
      7
      8
      9
      const double *f1(const double ar[],int n);
      const double *f2(const double [],int );
      const double *f3(const double *,int );
      //声明一个指针可以指向f1,f2,f3
      const double * (*p1)(const double *,int );//返回类型相同,函数的特征标相同
      //声明并初始化
      const double * (*p1)(const double *,int )=f1;
      //也可以使用自动类型推断
      auto p2=f2;
    1. 使用for循环通过指针依次条用每个函数

      例子:声明包含三个函数指针的数组,并初始化

const double * (*pa[3])(const double *,int)={f1,f2,f3};

问:为什么不使用自动类型推断?auto

答:因为自动类型推断只能用于单值初始化,而不能用初始化列表。

但可以声明相同类型的数组 auto pb=pa;

使用:

1
2
3
4
5
6
const double *px=pa[0](av.3);//两种表示法都可以
const double *py=pb[1](av.3);
//创建指向整个数组的指针。由于数组名pa是指向函数指针的指针
auto pc=&pa;//c++11
//等价于
const double * (*(*pd[3]))(const double *,int)=&pa;//C++98
  • 除了auto外,其他简化声明的工具,typedef进行简化
    点云库里常常用到,如:typedef pcl::PointNormal PointNT
1
2
typedef const double * (*p_fun)(const double *,int );
p_fun p1=f1;

二、函数探幽

C++11新特性

  1. 函数内联
  2. 按引用传递变量
  3. 默认参数值
  4. 函数重载(多态)
  5. 模板函数

1.内联函数

c++内联函数–>提高程序运行速度:常规函数与内联函数的区别在于,C++编译器如何将它们组合到程序中

  • 常规函数调用过程:
  1. 执行到函数调用指令程序在函数调用后立即存储该指令地址,并将函数参数复制到堆栈中(为此保留的代码),
  2. 跳到标记起点内存单元,
  3. 执行函数代码(也许将返回值放入寄存器中),
  4. 然后跳回地址被保存的指令处。

来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销。

  • 情况:函数代码执行时间很短—内联调用就可以节省非内联调用的大部分时间(节省时间绝对值并不大)
  • 代价:需要占用更多的内存:如果程序在是个不同地方调用一个内联函数,则该函数将包含该函数代码的10个副本
  • 使用:

在函数声明前加上关键字inline;

在函数定义前加上关键字inline;

通常的做法是省略原型,将整个定义(即函数头和所有代码),放在本应提供原型的地方。

  • 内联函数不能递归
  • 如果函数占用多行(假设没有冗长的标识符),将其作为内联函数不太合适.

内联与宏

C语言使用预处理语句#define来提供宏—内联代码的原始实现

1
# define SQUARE(X) X*X
  • 这不是通过传递参数实现的,而是通过文本替换实现的—X是”参数”的符号标记。所以宏不能按值传递

故有时候会出现错误

1
2
c=10;
d=SQUARE(C++);is replaced by d=C++*c++=11X12=122

2.按引用传递变量

引用变量–>是复合类型
int & rodents =rats;其中int &是类型,该声明允许将rats和rodent互换—他们指向相同的值和内存单元。

  • 必须在声明引用变量时进行初始化
  • 引用更接近const指针(指向const数据的指针),必须在创建时进行初始化,一旦与某个变量关联起来就一直效忠于它。
    1
    2
    3
    4
    5
    int & rodents=rats;
    //实际上是下述代码的伪装表示
    int * const pr=&rats;
    //引用rodents扮演的角色与*pr相同。
    //*pr值是个地址,且该地址恒等于&rat-->rats的地址

引用的属性与特别之处

应该尽可能使用const

C++11新增了另外一种引用—右值引用。这种引用可指向右值,是使用&&声明的:

第十八章将讨论如何使用右值引用来实现移动语义(move semantics),以前的引用(使用&声明的引用)现在称为左值引用

    1. 右值引用是对临时对象的一种引用,它是在初始化时完成的,但右值引用不代表引用临时对象后,就不能改变右值引用所引用对象的值,仍然可以初始化后改变临时对象的值
    1. 右值短暂,右值只能绑定到临时对象。所引用对象将要销毁或没有其他用户
    1. 初始化右值引用一定要用一个右值表达式绑定。

例子:

1
2
3
double &&rref=std::sqrt(36.00);//在左值引用中不成立,即使用&来实现也是不允许的
double j=15.0;
double&& jref=2.0*j+18.5;//同样使用左值引用是不能实现的。

将引用用于结构

引用非常适合用于结构和类(C++用户定义类型)而不是基本的内置类型。

  • 声明函数原型,在函数中将指向该结构的引用作为参数:void set_pc(free_throws & tf);如果不希望函数修改传入的结构。可使用const;void display(free_throws & tf);

  • 返回引用:free_throws &accumlate(free_throws& traget,free_throws& source);为何要返回引用?如果accumlate()返回一个结构,如:dup=accumlate(team,five) 而不是指向结构的引用。这将把整个结构复制到一个临时位置,再将这个拷贝复制给dup。但在返回值为引用时,直接把team复制到dup,其效率更高,复制两次和复制一次的区别。

  • 应避免返回函数终止时,不在存在的内存单元引用。为避免这种问题,最简单的方法是,返回一个作为参数传递给函数的引用。作为参数的引用指向调用函数使用的数据,因此返回引用也将指向这些数据。

    1
    2
    3
    4
    5
    6
    7
    free_throws& accumlate(free_throws& traget,free_throws& source)
    {
    traget.attempts+=source.attempts;
    traget.mode+=source.mode;
    set_pc(target);
    return target;
    }
  • 另一种方法是用new来分配新的存储空间

    1
    2
    3
    4
    5
    6
    7
    const free_throws& clone(&three)
    {
    free_throws * pt;//创建无名的free_throws结构,并让指针pt指向该结构,因此*pt就是该结构,在不需要new分配的内存时,应使用delete来释放它们。
    //auto_ptr模板以及unique_ptr可帮助程序员自动完成释放
    * pt=ft;
    return *pt;//实际上返回的是该结构的引用
    }

将引用用于对象

和结构同理

对象继承和引用

使得能够将特性从一个类传递给另外一个类的语言被称为继承

ostream–>基类 ofstream–>派生类

基类引用可以指向派生类对象,而无需强制类型转换

时使用引用参数

使用引用参数到主要原因有两个:

(1)程序员能够修改调用函数中的数据对象。

(2)通过传递引用而不是整个数据对象,可以提高程序的运行速度。

  当数据对象较大时(如结构和类对象),第二个原因最重要。这些也是使用指针参数的原因。这是有道理的,因为引用参数实际上是基于指针的代码的另一个接口。那么什么时候应该使用引用,什么时候应该使用指针呢?什么时候应该按值传递呢?下面是一些指导原则:

对于使用传递到值而不做修改到函数:

(1)如果数据对象很小,如内置数据类型或小型结构,则按值传递。

(2)如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。

(3)如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需要的时间和空间。

(4)如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。

对于修改调用函数中数据的函数:

(1)如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码(其中x是int),则很明显,该函数将修改x。

(2)如果数据对象是数组,则只能使用指针。

(3)如果数据对象是结构,则使用引用或指针。

(4)如果数据对象是类对象,则使用引用。

  当然,这只是一些指导原则,很可能有充分到理由做出其他的选择。例如,对于基本类型,cin使用引用,因此可以使用cin>>n,而不是cin>>&n。

3.默认参数值—当函数调用中省略了实参时自动使用的一个值

如何设置默认值?**必须通过函数原型

char* left(const char* str,int n=1);原型声明

定义长这样 char * left(const char* str,int n){…}

对于带参数列表的函数,必须从左向右添加默认值:下面代码错误,int j应该也设默认值

1
int chico(int n,int m=6,int j);//fault
  • 通过默认参数,可以减少要定义的析构函数方法以及方法重载的数量

4.函数重载

    1. 默认参数让你能够使用不同数目的参数调用的同一个函数。
    1. 而函数多态(函数重载)让你能够使用多个同名函数。
    1. 仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应用函数重载
    1. C++使用名称修饰(名称矫正)来跟踪每一个重载函数

未经过修饰:long MyFunction(int,float);

名称修饰(内部转换):?MyFunctionFoo@@YAXH—>将对参数数目和类型进行编码

重载与多态的区别

    1. 重载:是指允许存在多个同名方法,而这些方法的参数不同(特征标不同)。重载的实现是:编译器根据方法不同的参数表,对同名方法的名称做修饰,对于编译器而言,这些同名方法就成了不同的方法。他们的调用地址在编译器就绑定了。重载,是在编译阶段便已确定具体的代码,对同名不同参数的方法调用(静态联编)
  • C++中,子类中若有同名函数则隐藏父类的同名函数,即子类如果有永明函数则不能继承父类的重载。
    1. 多态:是指子类重新定义父类的虚方法(virtual,abstract)。当子类重新定义了父类的虚方法后,父类根据赋给它的不同的子类,动态调用属于子类的方法,这样的方法调用在编译期间是无法确定的。(动态联编)。对于多态,只有等到方法调用的那一刻,编译器才会确定所要调用的具体方法。

重载与覆盖的区别

  1. 重载要求函数名相同,但是参数列列表必须不不同,返回值可以相同也可以不不同。
    覆盖要求函数名、参数列列表、返回值必须相同。
  2. 在类中重载是同一个类中不同成员函数之间的关系
    在类中覆盖则是⼦子类和基类之间不同成员函数之间的关系
  3. 重载函数的调用是根据参数列表来决定调用哪一个函数 覆盖函数的调用是根据对象类型的不不同决定调用哪一个
  4. 在类中对成员函数重载是不不能够实现多态 在子类中对基类虚函数的覆盖可以实现多态

5.模板函数—通用的函数描述

  • 用于函数参数个数相同的类型不同的情况,如果参数个数不同,则不能那个使用函数模板

  • 函数模板自动完成重载函数的过程。只需要使用泛型和具体算法来定义函数,编译器将为程序使用特定的参数类型生成正确的函数定义

  • 函数模板允许以任意类型的方式来定义函数。例如,可以这样建立一个交换模板

    1
    2
    3
    4
    5
    6
    7
    8
    template <typename AnyType>
    void Swap(AnyType &a,AnyType &a)
    {
    AnyType temp;
    temp=a;
    a=b;
    b=temp;
    }
  • 模板不会创建任何函数,而只是告诉编译器如何定义函数

  • C++98没有关键字typename,使用的是template<class AnyType>void Swap(AnyType &a,AnyType &a){...}

  • 函数模板不能缩短可执行程序,最终仍将由两个独立的函数定义,就像以手工方式定义了这些函数一样。最终的代码不包含任何模板,只包含了为程序生成的实际函。使用模板的寒除湿,它使生成多个函数定义更简单,更可靠更常见的情形是将模板放在头文件中,并在需要使用模板的文件中包含头文件

重载的模板

对多个不同类型使用同一种算法(和常规重载一样,被重载的模板的函数特征标必须不同)。

1
2
3
4
template <typename T>
void Swap(T& a,T& b);
template <typename T>
void Swap(T* a,T* b,int n);
  • 模板的局限性:编写的模板很可能无法处理某些类型

    如1.T为数组时,a=b不成立;T为结构时a>b不成立

  • 解决方案:

  1. C++允许重载运算符,以便能够将其用于特定的结构或类
  2. 为特定类型提供具体化的模板定义

显式具体化(explicit specialization)

提供一个具体化函数定义,其中包含所需的代码,当编译器找到与函数调用匹配的具体化定义时,将使用该定义,不再寻找模板。

  • 该内容在代码重用中有不再重复。

重载解析(overloading resolution)—编译器选择哪个版本的函数

对于函数重载,函数模板和函数模板重载,C++需要一个定义良好的策略,来决定为函数调用哪一个函数定义,尤其是有多个参数时

过程:

  1. 创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数。
  2. 使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式的转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。例如,使用float参数的函数调用可以将该参数转换为double,从而与double形参匹配,而模板可以为float生成一个实例。
  3. 确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用出错。

最佳到最差的顺序:

  1. 完全匹配,但常规函数优先于模板
  2. 提升转换(例如,char和shorts自动转换为int ,float自动转换为double)。
  3. 标准转换(例如,int转换为char,long转换为double)。
  4. 用户定义的转换,如类声明中定义的转换。

完全匹配:完全匹配允许的无关紧要转换

从实参到形参 到实参
Type Type &
Type & Type
Type[] * Type
Type(argument-list) Type( * )(argument-list)
Type const Type
Type volatile Type
Type* const Type
Type* volatile Type
***
# C++

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×