function
函数只能被定义一次 但可以声明多次。若此函数容易不被使用,那么可以只有声明,没有定义。函数声名可以不写形参名,但其实在.h和.cpp文件时,并不会这样。
分离式编译,此过程中会产生一个.obj或.o(unix)的文件,后缀名的含义是该文件包含对象代码。
c++传值参数时,会拷贝,传指针和非引用类型时都是这样。函数中的指针,与实参是两个不同指针,只是运行时改变了 指针所值对象的值,二者又指向一个值,故看起来像是修改了,其实实参本身没有变
传引用参数
使用传引用可以避免数据的拷贝,函数参数最好声明为常量引用。定义为普通引用时,1. 会让人觉得函数会修改实参值, 2.会使const对象无法直接调用此函数,需转换类型才可以调用。
数组形参
数组的两个性质:1. 不允许拷贝数组,2. 使用数组时(通常)会将其转换成指针。故当函数传递数组时,实际上传递的是指向数组首元素的指针
// 尽管形式不同,三个print函数是等价的。
// 每个函数都有一个const int* 类型的形参
void print(const int*);
void print(const int[]);
void print(const int[10]); // 维度只是表示期望的数组个数,实际不一定
调用
int i = 0, j[2] = {0, 1};
print(&i); // &i 为int*
print(j); // j 转换为int*并指向j[0]
管理指针形参的三种常用技术(防止访问越界)
1、使用标记指定数组长度,类型处理c风格的字符串,遇到空字符停止。
2、传递数组首尾元素指针。
3、显式传递一个表示数组大小的形参。
数组引用形参
void print(int (&arr)[10]);
从内向外可知,参数为引用包含10个整型的数组的引用。此时调用此函数必须传递一个大小为10个数组。
含有可变形参的函数
1、当实参个数未知,类型相同时,可以使用initializer_list形参。
2、实参类型不同时,可通过使用可变参数模板
3、使用省略符,此方式一般只用于与C函数交互的接口程序。
initializer_list
#include "stdafx.h"
#include <initializer_list>
#include <string>
int main()
{
std::initializer_list<std::string> ls;
std::initializer_list<int> li;
return 0;
}
同vector一样,initializer_list也是一种模板类型,定义时必须说明所含元素类型,同时对象中的元素永远是常量值,无法改变。
使用:
#include "stdafx.h"
#include <initializer_list>
#include <string>
#include <iostream>
using std::string;
using std::initializer_list;
void error_meg(int, initializer_list<string>);
int main()
{
string expected = "zhu";
string actual = "zzu";
if (expected != actual)
error_meg(-1, {"functionX", expected, actual});
else
error_meg(0, { "functionX", "okay" });
return 0;
}
void error_meg(int other, initializer_list<string> ls)
{
for (auto beg = ls.begin(); beg != ls.end(); ++beg)
{
std::cout << *beg << " ";
}
}
返回值
不要返回局部对象的引用或指针,函数执行后会释放其存储空间。
const string & manip()
{
string ret = "123";
//return "empty"; 返回局部临时值引用
return ret; // 返回局部对象引用
}
引用返回左值
调用一个返回引用的函数得到左值,其他类型为右值。当返回的类型是非常量引用时,可以为函数赋值。
主函数返回值
当主函数没有写return时,编译器将隐式插入一条返回0的return语句。
尾置返回类型
#include "stdafx.h"
#include <initializer_list>
#include <string>
#include <iostream>
using std::string;
using std::initializer_list;
string(&getRef(int i))[10];
auto getRef2(int i)->string(&)[10];
int(*getPoint(int i))[10];
auto getPoint2(int i) -> int(*)[10]; // 确保函数声明的拼写正确
int main()
{
// 调用 getRef
std::string(&refStrings)[10] = getRef(0);
std::cout << "Strings from getRef:" << std::endl;
for (const auto& str : refStrings) {
std::cout << str << std::endl;
}
// 调用 getRef2
std::string(&refStrings2)[10] = getRef2(0);
std::cout << "Strings from getRef2:" << std::endl;
for (const auto& str : refStrings2) {
std::cout << str << std::endl;
}
// 调用 getPoint
int(*pointNumbers)[10] = getPoint(0);
std::cout << "Numbers from getPoint:" << std::endl;
for (int i = 0; i < 10; ++i) {
std::cout << (*pointNumbers)[i] << std::endl;
}
// 调用 getPoint2
int(*pointNumbers2)[10] = getPoint2(0);
std::cout << "Numbers from getPoint2:" << std::endl;
for (int i = 0; i < 10; ++i) {
std::cout << (*pointNumbers2)[i] << std::endl;
}
return 0;
}
// 返回 std::string 数组的引用
std::string(&getRef(int i))[10]{
static std::string strings[10] = {
"String 1", "String 2", "String 3", "String 4", "String 5",
"String 6", "String 7", "String 8", "String 9", "String 10"
};
return strings; // 返回字符串数组的引用
}
// 使用后置返回类型语法返回 std::string 数组的引用
auto getRef2(int i)->std::string(&)[10]{
static std::string strings[10] = {
"String A", "String B", "String C", "String D", "String E",
"String F", "String G", "String H", "String I", "String J"
};
return strings; // 返回字符串数组的引用
}
// 返回整型数组的指针
int(*getPoint(int i))[10]{
static int numbers[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
return &numbers; // 返回指向整数数组的指针
}
// 使用后置返回类型语法返回整型数组的指针
auto getPoint2(int i) -> int(*)[10]{
static int numbers[10] = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
return &numbers; // 返回指向整数数组的指针
}
函数重载
函数名称相同,但形参列表不同,称为重载函数。main函数不能重载。
void find(int i){};
//void find(const int i) {}; // 顶层const error
void find(int* i) {};
//void find(int* const i) {}; // 顶层const error
void find(const int* i) {}; // 底层const 作用于常量指针
void find(int& i) {};
//void find(int& const i) {}; // 顶层const error
void find(const int& i) {}; // 底层const 作用于常量引用
顶层const 形参,无法与另一个没有顶层const的形参区分开。
const_cast和重载
const string& shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
string& shorterString(string &s1, string &s2)
{
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
// 以引用对象的类型作为auto的类型时,会转为实际类型,所以需要加 &
return const_cast<string&>(r);
}
使用 const_cast 将实参强制转换成对const的引用,然后调用 shorterString 函数的const版本,在将其返回的 const string 的引用转回为普通string&,因引用事实上绑定在了某个初始的非常量实参上,故是安全的。
调用重载的函数
最有可能的三种结果:
1、编译器找到一个与实参**最佳匹配(best match)**的函数,并生成调用该函数的代码。
2、找不到任何一个函数与调用的实参匹配,此时编译器发出**无匹配(no match)**的错误信息。
3、有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误,称为二义性调用(ambiguous call)。
重载与作用域
string read();
void print(const string &);
void print(double); // 重载print函数
void fooBar(int ival)
{
bool read = false; // 新作用域: 隐藏了外层的read
//string s = read(); // 现在read 是一个布尔值
// 通常来说不应在函数作用域中声明函数,下面仅是为了说明作用域与重载的关系
//void print(int); // 隐藏了之前的print
print("Value: "); // void print(const string &); 被屏蔽
print(ival); // 正确
print(3.14); //正确,调用 print(int); print(double);被隐藏
}
调用print函数时,编译器开始寻找对该函数名的声明。**一旦在当前作用域中找到了所需的名字,编译器就会忽略掉外层作用域中的同名实体。**剩下的工作就是检查函数调用是否有效了。c++中,名字查找发生在类型检查之前。
特殊用途的语言特征
默认实参
默认实参后面的所有形参都必须有默认值。
内联函数
内联函数可避免函数调用的开销,编译过程中将其展开,避免了运行时开销。inline 只是向编译器发出的一个请求,编译器可以选择忽略此请求。
constexpr函数
能用于常量表达式的函数,但不一定返回常量表达式。但需遵守:函数的返回类型及所有形参的类型都得是字面值类型,并且函数体中必须有且只有一条return语句。
为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数。
函数指针
bool lengthCompare(const string&, const string &);
bool(*pf)(const string&, const string &); //指向一个函数,函数参数是两个const string引用,返回类型为bool。未初始化
bool* pf2(const string&, const string &); //声明一个名为pf的函数,该函数返回bool*
使用
bool lengthCompare(const string&, const string &);
bool(*pf)(const string&, const string &); //指向一个函数,函数参数是两个const string引用,返回类型为bool。未初始化
bool* pf2(const string&, const string &); //声明一个名为pf的函数,该函数返回bool*
int main()
{
pf = lengthCompare; // 指向
pf = &lengthCompare; // 与上面等价,取地址符为可选项
bool b1 = pf("hello", "goodbye");
bool b2 = (*pf)("hello", "goodbye");
bool b3 = lengthCompare("hello", "goodbye");
return 0;
}
bool lengthCompare(const string& s1, const string & s2)
{
return s1.size() > s2.size();
}
函数指针形参
和数组类似,虽不能定义函数类型的形参,但是形参可以是指向函数的指针。形参看似是函数类型,实际上却是当成指针使用:
void useBigger(const string &s1, const string &s2, bool pf(const string&, const string &)); // 会自动转换成指向函数的指针
void useBigger(const string &s1, const string &s2, bool (*pf)(const string&, const string &)); // 显示将形参定义成指向函数的指针
等价,但是因为是声明,所以也无所谓,但实现只能有一个。使用typedef精简:
bool lengthCompare(const string&, const string &);
// 定义函数类型
typedef bool Func(const string&, const string &);
typedef decltype(lengthCompare) Func2; //与上相同
// 定义指向函数的指针
typedef bool(*FuncP)(const string&, const string &);
typedef decltype(lengthCompare) *FuncP2; // 与上相同,因decltype结果为函数类型,故需要在结果前面加上*得到指针。
void useBigger(const string &s1, const string &s2, Func) {};
void useBigger(const string &s1, const string &s2, FuncP2);
decltype作用于某个函数时,它返回函数类型而非指针类型。需显式加上*以表明我们需要返回指针,而非函数本身。
返回指向函数的指针
和数组类似,虽无法返回一个函数,但能返回指向函数类型的指针。使用别名:
using F = int(int*, int); // F是函数类型,非指针
using FP = int(*)(int*, int); // PF是指针类型
FP f1(int); // f1返回指向函数的指针
// F f1(int); // 错误,F为函数类型,f1不能返回一个函数
F *f1(int); // 正确,显式地指定返回类型是指向函数的指针
int(*f1(int))(int*, int); // 直接声明
auto f1(int)->int(*)(int*, int); // 尾置返回类型
直接声明的形式,从内向外阅读可知,f1有参数列表,故为一个函数;f1前面有一个*,故f1返回一个指针;进一步观察发现,指针的类型本身也包含形参列表,故指针指向函数,该函数有两个参数,函数返回类型为int。
文章评论