^(* ̄(oo) ̄)^

  • 首页
  • 博客
  • 文档
  • 网盘
  • 仓库
  • 简介
Keep Learning
  1. 首页
  2. 博客
  3. 正文

c++ - function

2024年10月14日 373点热度

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

image-20241013173228499

#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 << " ";
	}
}

image-20241013174721148

返回值

不要返回局部对象的引用或指针,函数执行后会释放其存储空间。

const string & manip()
{
	string ret = "123";
	//return "empty"; 返回局部临时值引用
	return ret; // 返回局部对象引用
}

image-20241013175459667

引用返回左值

调用一个返回引用的函数得到左值,其他类型为右值。当返回的类型是非常量引用时,可以为函数赋值。

主函数返回值

当主函数没有写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。

标签: c++
最后更新:2024年10月14日

九州安所如

程序员

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2023 zhu

鲁ICP备2026022547号