欢迎来到森的博客

十年磨一剑
扩大
缩小

C++Essential 个人总结(二)

第二章

2.1、如何编写函数

函数组成:返回类型,函数名,参数列表,函数体
void foo(int i){...}
函数必须先被声明,然后才能被调用(被使用),函数声明不必提供函数体,但必须指明返回类型、函数名、以及参数列表——函数原型function prototype

//函数声明
int fibon_elem( int pos);

函数的定义则包含函数原型和函数体
编写函数需要注意的两个问题

  • 输入有效性检查
    用户可能会错误输入,导致程序无法运行
  • 内存溢出
    每个值类型只能表示值域内最小至最大值间的数
代码实现
#include <iostream>
using namespace std;
bool fibon_elem( int ,int &);
bool fibon_elem( int pos,int &elem)
{
  elem =1;
  int n_1=1,n_2=1;
  if (pos<0||pos>1024)
    return false;

  for( int ix=3; ix<=pos;ix++)
  {
    elem=n_1+n_2;
    n_1=n_2;n_2=elem;
  }
  return true;
}
int main()
{
int pos;
cin>>pos;
int elem;
if( fibon_elem(pos,elem))
      cout <<"element # " <<pos<<" is "<<elem<<endl;
else
      cout <<"Sorry, couldn't calculate"<<endl;
}
---- ## 2.2、调用函数 ### 参数传递 两种参数传递方式:**传址(by reference)、传值(by value)** *相关概念:当程序被调用时,会在内存中建立特殊区域,称为“程序堆栈”,这块区域提供了每个函数参数的存储空间,也提供了函数定义的每个对象的内存空间——这些对象称为局部对象(local object),一旦函数完成,内存被释放掉,从程序堆栈中pop出来* 传值:将传入的对象的值复制了一份,成为了参数的局部性定义(local definition),传入的对象和和操作的参数只有值相等而已,没有任何关系。 传址:将函数的参数和实际传入的对象产生关联,即传址(pass by reference) **传址实现方式:** 1. 将参数声明为一个reference; `void swap3(int &vall, int &val2)` 在类型名称和reference名称之间插入&符号,即声明了一个reference,reference对象一旦设定后就不能改变。对reference的所有操作都和面对“reference所代表的对象”进行的操作一致。 将参数声明为reference的理由: + 希望直接对传入的对象进行修改 + 降低复制大型对象的额外负担 2. 使用pointer作为参数 与reference的效果相同:传递的是对象地址,而不是整个对象的副本 **pointer 与 reference 参数的差异**:pointer可能不指向任何实际对象,当提领pointer时,要确定其值**非零**,而reference一定会代表某个对象 PS:除非希望在函数内更改参数值,否则在传递内置类型(诸如:int,double等变量)时,不要使用传址方式。传址机制主要用于传递class object ### 作用域及范围 对象在程序内的存活区域成为该对象的scope(作用域) 除了static,一般在函数内定义的对象,只存在函数执行期间,被称为在函数内有 local scope,函数外不存在,对象如果在函数以外声明,具有所谓的file scope,内置类型的对象,如果具有file scope,必定被初始化为0,如果被定义在local scope内,除非被指定初值,否则不会被初始化。 **值得一提的是:在返回函数中创建的对象的时候,应该使用传值方式返回** ### 动态内存管理 除了local scope 和 file scope,还有第三种储存形式称为 dynamic extend(动态范围),其内存由程序空余空间分配而来,也成为heap memory(堆内存),其分配通过new 表达式完成,释放由 delete 完成 new表达式形式 `new Type `或`new Type(initial_value);` 例子: ``` int *pi; pi = new int; //无需检验pi是否为0 pi =new int(1024); //指定初值 int* pia=new int[24]; //从heap中分配数组 delete pi; //释放指定对象; delete [] pia; ``` 如果程序员不使用delete表达式,分配的对象永远不会消失,称为内存泄漏(memory leak); ## 2.3、提供默认参数 默认参数的设定规则: 1、默认值的解析(resolve)操作由最右边开始进行,即我们为某个参数提供了默认值,那这一参数的右侧所有参数也必须有默认参数才行 2、默认值只能指定一次,可以在函数声明处,亦可以在函数定义处,但不能在两个地方都指定 通常将函数的默认值放到函数声明处,函数声明会放到头文件。 ## 2.4、使用局部静态对象 为节省函数间的通信问题而将对象定义于file scope内,会打乱不同函数间的独立性,使得难以理解,是一种冒险。可以选择使用局部静态对象(local static object) ``` const vector* fibon_seq( int size) { static vector elems; // 函数的工作逻辑 return &elems; } ``` 局部静态对象所处的内存空间,即使在不同的函数调用过程中,依然持续存在。
代码实现
const vector <int>* fibon_seq(int size)
{
  const int max_size=1024;
  static vector<int> elems;
  if (size<=0||size>max_size)
  {
    std::cerr << "fibon_seq():oops:invalid size" << '\n';
    return 0;
  }
  for (int ix=elems.size();ix<=max_size;ix++)
  {
    if (ix==0||ix==1)
      elems.push_back(1);
    else elems.push_back(elems[ix-1]+elems[ix-2]);
  }
  return &elems;
}

2.5、声明inline函数

内联函数(inline function):将函数声明为inline,表示在函数每个调用点上,将函数的内容展开,面对一个inline函数,编译器将函数的调用操作改为一份函数代码副本代替,只要在函数前面加上关键词inline,便可将函数声明为inline
最适合声明为inline的函数,是体积小,常被调用的从事的计算不复杂的函数

2.6、 提供重载函数

函数重载(function overloading):参数列表不相同(参数类型或参数个数)的两个或多个函数可以拥有相同的函数名称,但编译器不能根据函数返回类型区分两个具有相同名称的函数

2.7、 定义并使用模板函数

function template(函数模板)以关键字template开头,以成对尖括号<>包围起来的一个或多个标识符

template <typename elemType> void display_message( const string &msg, const vector<elemType> &vec)
{
      //函数体
}

关键词typename表示,elemType是函数中的暂时放置类型的占位符,可以任意取名字。
function template的参数列表通常都由两种类型构成:一类是明确的类型,另一类是暂缓决定的类型。
使用方式

vector <int> ivec; string msg;
display_message(msg,ivec);

编译器会将elemType绑定为int类型,产生一份display_message()函数实例;
一般而言,如果函数具备多种实现方式,可以将其重载(overload),每份实例提供的是相同的通用服务,如果希望代码主体不变,仅仅更改数据类型,可通过function template达到目的。

2.8、函数指针(pointer to function)

  • 定义
    函数指针必须指明函数的返回类型及参数列表,函数指针的定义必须将*放在某个位置,表示定义的是指针
//要指向的函数
const vector<int> *fibon_seq(int size);
//函数指针
const vector<int>* (*seq_ptr)(int);

函数的地址可以直接通过函数的名称获得。
关键词enum可以用来定义枚举类型(enumerated type),默认情况下,第一个枚举项的值为0,其后逐渐增加1.
enum nstype{ns_fibon,ns_clus,ne_pell};

2.9设定头文件

头文件的扩展名,习惯上为.h,标准库例外,没有扩展名。
函数定义只能有一份,除了inline函数,为了扩展inline函数,每个调用点上都要获得其定义,所以inline函数的定义放在头文件中;还有const object,const object 只要一出定义文件之外就不可见,所以在多个程序代码中定义,不会互相干扰出错误。
函数在头文件中想要标识为声明,需要在前面加上关键字extern。
头文件如果是<>,表明该头文件被认为是标准的或项目专属的头文件,编译器编译时优先从某些默认的磁盘目录中寻找,如果时“”,此文件便被认为是用户提供的头文件,有先从包含此文件的文件所在磁盘目录找起。

posted on 2021-02-05 23:37  森艾雅  阅读(168)  评论(0)    收藏  举报

导航