目录
1 参数传递
1.1 非引用形参
1.1.1 指针形参
#include<iostream>
using namespace std;
int gcd(int* v1, int* v2)
{
while (*v2) {
int temp = *v2;
*v2 = *v1 % *v2;
*v1 = temp;
}
return *v1;
}
int main()
{
int a = 56, b = 16;
cout << gcd(&a, &b) << endl;
return 0;
}
1.1.2 const形参
void fun(const int i);
则在函数fun中i为不可改变的常量。但实际上为了对c语言的兼容,具有const形参或非const的形参的函数并无区别。所以,既可以传递const形参,也可以传递非const形参。
1.2 引用形参
1.2.1 利用const引用避免复制
较大的数组或者类类型会使得复制变得十分低效,并且有的类类型是无法复制的。故使用引用形参,函数可以间接的访问实参对象,而不需要复制。
bool isShorter(const string &s1, const string &s2)
{
return s1.size()<s2.size();
}
warning:应该将不需要修改的引用形参定义为const引用。普通的非const引用形参在使用时不太灵活,其既不可以用const对象初始化,也不可以用字面值或者产生右值的表达式实参初始化。
1.2.2 传递指向指针的引用
int swap(int *&a, int *&b)
{
int *temp = a;
a = b;
b = temp;
}
形参 int *&a 应该从右向左理解,a是个引用,与一个指向int类型的指针相关联。
实际上,a是传进swap函数的任意指针的别名。
1.3 vector和其他容器类型的形参
c++倾向于使用容器的迭代器来处理容器。
void print(vector<int>::const_iterator beg, vector<int>::const_iterator end)
{
for (vector<int>::iterator i = end; i != end; ++i)
cout << *i << endl;
return;
}
1.4 数组形参
特点:1. 数组无法复制(所以无法编写形参为数组的函数:形参通过复制初始化为实参,而数组是不能进行复制的)
2. 数组的数组名是指向数组第一个元素的指针
1.4.1 数组形参的定义
void printValues(int*); //比较好的定义方式,指明了通过指针来传递数组内容
void printValues(int[]); // 数组的长度是被忽略的
void printValues(int[10]);
1.4.2 通过引用传递数组
如果形参是数组的引用,那么不会将数组实参转化为指针,而是传递数组的引用本身。
此时编译器会检测数组实参的大小与形参的大小是否匹配。
void printValues(int(&arr)[10])
{
for (size_t i = 0; i != 10; i++)
cout << arr[i] << endl;
return;
}
int main()
{
int i = 0, j[2] = { 0, 1 };
int k[10] = { 0,1,2,3,4,5,6,7,8,9 };
printValues(k);
return 0;
}
关于形参的定义,由于下标操作符具有更高的优先级,括号是必不可少的。如果没有括号:
void printValues(int &arr[10]);
那么arr就是一个有10个元素,每个元素都是和int对象相关联的引用的数组。
1.4.3 多维数组的传递
理解语句 int (*matrix)[10];
上面的语句将matrix定义为一个指针,指向一个有10int元素的数组。
void printValues(int matrix[][10], int rowsize);
void printValues(int (*matrix)[10], int rowsize);
2 return语句
2.1 返回非引用类型
函数的返回值用于初始化在调用函数处创建的临时对象(temportay object)。与形参的初始化相同,如果函数的返回值不是引用类型的,那么在调用函数的地方会将函数的返回值复制给临时对象。
2.2 返回引用类型
当函数返回引用类型时,没有复制返回值。而是返回对象本身。
const string &shorterstring(const string &s1, const string &s2)
{
return s1.size() <s2.size() ? s1 : s2;
}
2.2.1 不要返回局部对象的引用或指向局部对象的指针
考虑下面的代码:
string &manip(const string& s)
{
string ret = s;
return ret;
}
之前了解过,函数的局部对象在函数执行完毕之后所占用的空间会被释放,也就是说这个函数返回指向了一个不再有效的内存空间。
同样的,不要返回指向局部对象的指针,一旦局部对象释放,返回的指针就变成了指向不再存在对象的悬垂指针。
3 函数声明
与变量类似,一个函数只能定义一次,但可以声明多次。
函数应当在头文件中声明,在源文件中定义(应包含声明该函数的头文件)。
3.1 默认实参
warning:
1. 如果形参表中的一个形参具有默认参数,那么它后面的所有形参都必须有默认实参。
string screeninit(string::size_type height = 24,
string::size_type width = 80,
char background = ' '); // right
string screeninit(string::size_type height = 24,
string::size_type width = 80,
char background); // wrong
2. 使用带有默认实参的函数时,可以为形参提供实参,也可以不提供。如果提供了,那么就会覆盖原有的默认值;否则将使用默认的实参值。
string screen;
screen = screeninit(); // call screeninit(24, 80, ' ')
screen = screeninit(66); // call screeninit(66, 80, ' ')
screen = screeninit(66, 256); // call screeninit(24, 256, ' ')
screen = screeninit(66, 256, '#'); // call screeninit(66, 256, '#')
3. 默认实参只能用来填充缺少的末尾形参,如果给一个形参提供实参,那么这个形参前面的形参也都需要提供实参。
screen = screeninit( , , '?'); // error
screen = screeninit('?'); // call screeninit('?', 80, ' ')
4. 在一个文件中,只能为一个形参指定一个默认的实参一次。
/* wrong example */
//ff.h
int ff(int i = 0);
//ff.cc
#include"ff.h"
int ff(int i = 0){...}
4 局部对象
4.1 自动对象
定义:只有当定义它的函数被调用时才存在的对象称为自动对象(automatic object)。
自动对象和形参随函数的调用而创建,结束而撤销。
4.2 静态局部对象
static局部对象(static local object)不会被撤销。在多次调用定义静态局部对象的过程中,静态局部对象会持续存在并保存它的值。下面一个小程序,用来计算其调用自身的次数:
/*count the nums of calling this fun*/
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;
return 0;
}
5 类的成员函数
class Sales_item {
public:
double avg_price() const;
bool same_isbn(const Sales_item& rhs) const
{ return isbn == rhs.isbn;}
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
5.1 定义成员函数的函数体
类的所有成员都必须在类定义的花括号里声明,此后,无法再增加任何成员。
类的成员函数可以再类的定义内定义(当作内联函数),也可以在类的定义外定义。
5.1.2 成员函数含有额外的、隐含的形参
考虑语句:
bool same_isbn(const Sales_item& rhs) const
{ return isbn == rhs.isbn;}
通过total对象来调用same_isbn函数:
if(total.same_isbn(trans)){...}
通过trans初始化形参rhs,易得rhs.isbn为trans.isbn的引用。
而没有前缀的 isbn 使用了相同的实参绑定过程,使之与名为 total 的对象绑定起来。每个成员函数都有一个额外的、隐含的形参this将该成员函数与调用该函数的类对象捆绑在一起。当调用名为total 的对象的same_isbn 时,这个对象也传递给了函数。而same_isbn 函数使用 isbn 时,就隐式地使用了调用该函数的对象的isbn 成员。
5.1.3 const成员函数的引入
double avg_price() const;
bool same_isbn(const Sales_item& rhs) const
跟在函数形参表后面的const改变隐含的this形参的类型。
使用const后,this形参将是一个指向total对象的const Sales_Item*类型的对象的指针。与下相同:
// pseudo-code illustration of how the implicit this pointer is used
// This code is illegal: We may not explicitly define the this pointer ourselves
// Note that this is a pointer to const because same_isbn is a const member
bool Sales_item::same_isbn(const Sales_item *const this,
const Sales_item &rhs) const
{ return (this->isbn == rhs.isbn); }
因此函数same_isbn和avg_price只能读取而不可以修改调用该函数的对象的数据成员。
5.1.4 可以显示的指出this指针,但是没必要
bool same_isbn(const Sales_item& rhs) const
{ return this->isbn == rhs.isbn;}
bool same_isbn(const Sales_item& rhs) const
{ return (*this).isbn == rhs.isbn;}
5.2 在类外定义成员函数
在类的定义外面定义成员函数必须指明它们是类的成员:
double Sales_item::avg_price() const
{
if (units_sold)
return revenue / units_sold;
else
return 0;
}
函数名:
Sales_item::avg_price
使用作用域操作符指明函数 avg_price 是在类 Sales_item 的作用域范围内定义的。
形参表后面的 const 则反映了在类 Sales_item 中声明成员函数的形式。在任何函数定义中,返回类型和形参表必须和函数声明(如果有的话)一致。对于成员函数,函数声明必须与其定义一致。如果函数被声明为 const 成员函数,那么函数定义时形参表后面也必须有 const。
5.3 构造函数
5.3.1 构造函数是特殊的成员函数
构造函数是特殊的成员函数,与其他成员函数不同,构造函数和类同名,而且没有返回类型。
而与其他成员函数相同的是,构造函数也有形参表(可能为空)和函数体。
一个类可以有多个构造函数,每个构造函数必须有与其他构造函数不同数目或类型的形参。
5.3.2 构造函数的定义以及初始化列表
public:
double avg_price() const;
bool same_isbn(const Sales_item& rhs) const
{
return isbn == rhs.isbn;
}
// constructor
Sales_item() : units_sold(0), revenue(0.0) { }
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
在冒号和花括号之间的代码称为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。
它跟在构造函数的形参表之后,以冒号开关。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初始值。多个成员的初始化用逗号分隔。
具有类类型的成员皆被其默认构造函数自动初始化。于是,isbn 由string 类的默认构造函数初始化为空串。也可以显式指明。
解释了初始化列表后,就可以深入地了解这个构造函数了:它的形参表和函数体都为空。形参表为空是因为正在定义的构造函数是默认调用的,无需提供任何初值。函数体为空是因为除了初始化units_sold 和revenue 成员外没有其他工作可做了。初始化列表显式地将
units_sold 和revenue 初始化为 0,并隐式地将isbn 初始化为空串。当创建新 Sales_item 对象时,数据成员将以这些值出现。
5.3.3 合成的默认构造函数
如果没有为一个类显式定义任何构造函数,编译器将自动为这个类生成默认构造函数。
5.3.4 类代码文件的组织
通常将类的声明放置在头文件中。大多数情况下,在类外定义的成员函数则置于源文件中。
C++ 程序员习惯使用一些简单的规则给头文件及其关联的类定义代码命名。类定义应置于名为 type.h 或type.H 的文件中,type指在该文件中定义的类的名字。
成员函数的定义则一般存储在与类同名的源文件中。依照这些规则,我们将类 Sales_item 放在名为 Sales_item.h 的文件中定义。
任何需使用这个类的程序,都必须包含这个头文件。而 Sales_item 的成员函数的定义则应该放在名为 Sales_item.cc 的文件中。这个文件同样也必须包含Sales_item.h 头文件。
6 重载函数
出现在相同作用域的两个函数,如果具有相同的名字而形参不同,则称为重载函数(overloaded function)。
重载函数的返回值可以不同。
局部的重载函数会屏蔽在外层声明的同名函数。
7 指向函数的指针
void (*p)(int& a, int& b);
定义一个指针p指向一个返回void,形参为两个int类型的引用的函数。
7.1 使用typedef简化函数的定义
typedef void (*point)(int& a, int& b);
定义了一个指针类型point。。。
7.2 指向函数的指针的初试化和赋值及通过指针调用函数
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 2, b = 3;
typedef void (*point)(int& a, int& b); /* equal to: */
point p = swap; /* void (*p)(int& a, int& b) = swap; */
p(a, b); /* equal to : (*p)(a,b); */
cout << a << b << endl;
return 0;
}