最全【c ++ primer 笔记】第6章 函数_小呆鸟学习笔记,2024年最新阿里P8手把手教你

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 通过引用可以避免拷贝(如果对象太长,可以使用引用来避免拷贝,拷贝太浪费内存了)
  • 如果无需改变引用形参的值,最好将其声明为常量引用
  • 引用形参直接关联到绑定的对象,而非对象的副本。
  • 使用引用形参可以用于返回额外的信息。
//既返回位置也返回次数,此时可以给函数传入一个额外的引用实参,令其保存字符出现的次数
string::size_type find\_char(const string &s, char c, string::size_type &occurs)
{
	auto size = s.size();
	occurs = 0;
	for (int i = 0; i != s.size(); i ++)
	{
		if (s[i] == c)
		{	
			if (ret == s.size()) ret = i;
			++ occurs;
		}
	}
	return ret;            //出现次数通过occurs隐式地返回
}

🎨6.2.3 const 形参和实参
  • 实参初始化形参时以及进行拷贝时,都会忽略掉顶层const,因此导致void func (const int i);调用既可以传入const int也可以传入int

加深理解:引用是没有顶层 const 的,因此顶层 const 适用于指针及其他类型,对于传值来说传递的是实参的副本,无论如何都不会改变实参,因此形参加不加顶层 const 都是一样的。

void fcn(const int i ){ }
void fcn(int i ){ }         //错误:重复定义了fcn(int)

指针或引用形参与const

  • 使用非常量初始化一个底层const对象,但是反过来不行。
  • 普通引用必须用同类型的对象初始化。

加深理解:不能把普通引用绑定到const 对象上,不能把普通的引用绑定在字面值上。

void reset(int &r) { };
const int i = 10; 
reset(i);    //错误:不能把普通引用绑定到const 对象上
reset(42);   //错误:不能把普通的引用绑定在字面值上

尽量使用常量引用

  • 尽量使用常量引用做形参(注意常量引用的 const 是底层 const。)
  • 可以用字面值初始化常量引用

使用常量引用的优点

  1. 想要调用引用版本的reset,只能使用int 类型对象。而不能使用字面值、求值结果为int 的表达式、需要转换的对象或者const int 类型的对象。
  2. 想要调用指针班的reset 只能使用 int *

不能把 const 对象、字面值或需要类型转换的对象传递给普通的引用形参。但是可以传递给常量引用形参。

注意: 如果函数 a 把形参定义为了常量引用,函数 b 形参是普通引用,那么不能在 a 中使用 b 调用该常量引用形参。

bool  is\_sentence(const string &s)
{
	//如果在s的末尾有且只有一个句号,则s是一个句子
	string :: size_type ctr = 0;
	return find\_char(s, '.', ctr) == s.size() - 1 && ctr == 1;
}

错误因为is_sentence是const对象而find_char是普通对象

🎨6.2.4 数组形参

数组的两个特殊性质:不允许拷贝数组、使用数组时常会将其转换成指针

  • 因为数组不能进行拷贝,所以无法使用值传递的方式使用数组参数。因为数组会被转换成指针,所实际上传递的是指向数组的首元素指针。
void print(const int\*);
void print(const int[]);
void print(const int[10]);//三种声明等价,数字 10 没有什么实际影响

int i= 0, j[2] = {0, 1};
printf(&i);               //正确:&i的类型是int \*
printf(j);                //正确:j转换成int \* 并指向j[0]
                       //传入的是一个数组,那么实参会自动转换成指向数组首元素的指针

使用数组做形参确保数组访问不越界的方法:

  • 使用一个结束标记指定数组已结束,典型代表为 C 风格字符串
  • 传递指向数组首元素和尾后元素的指针
  • 显示传递一个表示数组大小的形参

数组引用形参

  • 可以定义数组的引用,但是没有引用数组,因为引用不是一个对象。注意数组的大小是构成数组类型的一部分
func(int &arr[10]);  //错误:arr 是引用的数组
func(int (&arr)[10]);//正确:array 是包含 10 个整数的整型数组的引用

int \*arr[10];        //指针的数组
int (\*arr)[10];      //指向数组的指针

🎨6.2.5 main:处理命令行选项
  • int main(int argc, char *argv[]){...}
  • 第一个形参代表数组中字符串的数量;第二个形参argv是一个数组,它的元素是指向C风格字符串的指针
🎨6.2.6 含有可变形参的函数

无法预知应该向函数传递几个实参,处理不同数量实参的主要方法有俩种:

  • 如果所有实参类型相同,传递一个 initializer_list 类型
  • 使用省略符形参,它可以传递可变数量的实参,注意它一般仅用于与 C 函数交互的接口程序

initializer_list 形参特点

  • initializer_list 也是一种模板类型,定义在同名的头文件中。
  • initializer_list 与 vector 容器大致相同,但是它的元素都是常量值
  • initializer_list 对象只能使用花括号初始化
  • C++ 里的 vector 等各类容器使用列表初始化时本质上都是通过一个采用了 initializer_list 形参的构造函数进行初始化的。
操作解释
initializer_list<T> lst;默认初始化;T类型元素的空列表
initializer_list<T> lst{a,b,c...};lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const
lst2(lst)拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。
lst2 = lst同上
lst.size()列表中的元素数量
lst.begin()返回指向lst中首元素的指针
lst.end()返回指向lst中微元素下一位置的指针
void err\_msg(ErrCode e, initializer_list<string> il){
    cout << e.msg << endl;
    for (auto bed = il.begin(); beg != il.end(); ++ beg)
        cout << \*beg << " ";
    cout << endl;
}

err\_msg(ErrCode(404), {"functionX", "okay});

如果向 initailizer_list 形参中传递一个值的序列,必须把序列放在花括号里。

void func(initializer_list<int> il)
func({3,4,5,2});

省略符形参

  • 省略符形参仅用于 C 和 C++ 通用的类型,大多数类类型的对象传递给省略符形参都无法正确拷贝。
  • 省略符形参只能出现于形参列表的最后一个位置
void func(parm_list,...);
void func(...);

🎯6.3 返回类型和 return 语句

  • 与参数传递一样,不能返回数组,只能返回指向数组的指针或数组的引用

return 俩个作用:

  • 返回 return 语句中的值
  • 终止当前正在执行的函数,将控制权返回到调用该函数的地方
🎨6.3.1 无返回值函数
  • 没有返回值的 return语句只能用在返回类型是 void的函数中
  • 返回 void的函数不要求非得有 return语句。因为它会在最后一句后面隐式地执行 return。
🎨6.3.2 有返回值函数
  • return语句的返回值的类型必须和函数的返回类型相同,或者能够隐式地转换成函数的返回类型
  • 在含有 return 语句的循环和条件后面也应该有一条 return 语句
  • 返回一个值和初始化一个变量或形参的方式一样,返回的值用于初始化调用点的变量。
  • 不要返回局部对象的引用和指针

因为局部对象在函数调用完成后,他所占用的存储空间被释放,此时函数终止意味着局部变量的引用将指向不再有效的内存区域

const string& func()
{
	string s;
	s = "xiaodainiao"
    return s;          //错误,字符串字面值转换成一个局部临时变量,不能返回局部对象的引用
}      

bool str\_cmp(const string &str1, const string &str2)
{
   for (int i = 1; i < n; i ++)
   {
	   if (str[1] != str[2])
	   return;               //错误
   }
   return true;              //这个往往会被忽略,在含有 return 语句的循环和条件后面也应该有一条 return 语句
}

引用返回左值

  • 返回引用的函数返回的是左值,其他返回类型得到右值
  • 可以为返回类型是非常量引用的函数的结果赋值
char &get\_val(string &str, string::size_type ix)
{
	return str[ix];
}
get\_val(s,0) = 'A';

列表初始化返回值

vector<string> process()
{
    string s;
    if(expected.empty())
        return {};// 返回一个空 vector 对象
    else if(expected == actual)
        return {"funcitonX","okay"};//返回一个列表初始化的 vector 对象 
}

main 的返回值

  • 如果结尾没有return,编译器将隐式地插入一条返回0的return语句
  • 返回0代表执行成功,返回其它值代表失败。
  • cstdlib 头文件定义了两个预处理变量来表示成功与失败
return EXIT_FAILURE;//失败
return EXIT_SUCCESS;//成功

🎨6.3.3 返回数组指针
  • 函数不能返回数组,但是可以返回数组的指针或引用
int \*p[10];   // 错误,指针的数组,p是一个含有10个指针的数组
int (\*p)[10]; // 正确,数组的指针,p是一个指针,指向含有10个整数的数组
int &p[10];   //错误,引用数组,引用不是对象,没有引用的数组,有数组的引用
int (&p) [10]; // 正确,数组引用

int(*function(int i )[10]);

  • func(int i ) 表示调用func函数需要传递一个int 实参
  • (*func(int i )) 对函数调用的结果执行解引用
  • (*function(int i )[10])表示解引用结果是一个大小为10的数组
  • int(*function(int i )[10])表示数组中的元素是int 类型

有三种方法简化返回数组指针或引用。

  • 使用类型别名
  • 使用尾置返回类型
  • 使用 decltype

使用类型别名

typedef int arrT[10]; // arrT 表示含有 10 个整数的数组
using arrT = int[10]; //等价声明
arrT\* func(int i); // 函数 func 返回一个指向含有 10 个整数的数组的指针

使用尾置返回类型

  • 任何函数的定义都可以使用尾置返回,适用于返回类型复杂的函数
auto func(int i) -> int(\*)[10];
//func接受一个int 类型的实参,返回一个指针,该指针指向含有10个整数的数组

使用 decltype

  • 如果已知函数返回的指针将指向哪个数组,可以使用 decltype
int odd[]={1,3,5,7,9};
decltype(odd)\* func(int i);//返回一个数组指针

注意 decltype 的结果是一个数组,要想表示func返回指针还必须在函数声明时加一个*号。

🎯6.4 函数的重载

  • main 函数不能重载
  • 函数名字相同,但是形参数量形参类型不同,此时发生重载。
  • 函数返回值类型不同,不是函数重载

重载和const形参

  • 函数重载无法区分顶层 const 形参和非顶层 const 形参,但是可以区分底层 const 形参与普通形参
int func(int i);
int func(const int i);//顶层const,无法区分,相当于重复声明

int func(int\* p);
int func(int\* const p);//顶层const,无法区分,相当于重复声明

int func(int\* p);
int func(const int\* p);//底层const,可以区分,一个普通指针,一个常量指针

int func(int& i);
int func(const int& i);//底层const,可以区分,一个普通引用,一个常量引用

const_cast

  • 强制类型转化,可以将常量引用转换为非常量引用,也可以将非常量引用转换为常量引用
  • 当要重载常量引用与非常量引用的版本时,在非常量引用的版本中可以通过 const_cast 将参数和返回值从常量引用转换为非常量引用,以实现对常量引用版本的调用。
string& s;
const_cast <const string&> (s);// 将 s 转换为常量引用
const_cast <string&> (s);// 将 s 转换回非常量引用

const string &str(const string &s1, const string &s2)
{
	return s1.size() > s2.size();
}

 string &str( string &s1,  string &s2)
 {
 	auto &r = str(const_cast<const string&> (s1), const_cast<const string&> (s2))
 	return const_cast<string&> (r);
 }

  1. 将实参转换为const的引用,然后调用str的const版本。
  2. 当返回时,因为返回类型是非常量引用,所以需要再次转换一下。
6.4.1 重载与作用域
  • 不同的重载版本要定义在同一作用域中(一般都是全局)

🎯6.5 特殊用途语言特性

🎨6.5.1 默认实参
  • 一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。(反过来就是设置默认值的形参必须都放在没有默认值的形参后面。)
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');

  • 如果想使用默认值实参时,只要在调用该函数的时候省略该实参就可以(默认实参负责填补函数调用时缺少的尾部实参。)
  • 局部变量不能作为默认实参,全局变量和字面值都可以。
  • 如果函数有默认实参,则调用函数时传入的实参数量可能少于它实际使用的实参数量
  • 通常应该在函数声明中指定默认实参,并将该声明放在合适的头文件中
string screen(sz, sz, char = ' ');
string screen(sz, sz ,char = '\*');   //错误重复声明,不能修改已经存在的默认值

🎨6.5.2 内联函数和constexpr函数
  • 内联函数可避免函数调用时的开销
  • 内联函数适合于规模小、流程直接、频繁调用的函数。

使用函数的缺点

  • 调用函数更慢,调用前要先保存寄存器(保护现场),并在返回时恢复:可能需要拷贝实参;程序转向一个新的位置继续执行。

在函数前用 inline 声明一下即表明是内联函数

🎨constexpr函数
  • 只能用于常量表达式的函数。但是 constexpr 函数不一定返回常量表达式。
  • constexpr int new_sz() {return 42;}
  • constexpr 函数的返回类型及所有的形参类型都必须是字面值类型,函数体中必须有且只有一条 return 语句
  • constexpr函数应该在头文件中定义。
  • 应该把内联函数和constexpr函数的定义放到头文件里。

🎯6.6 函数匹配

  • 重载函数匹配的三个步骤:1.候选函数;2.可行函数;3.寻找最佳匹配。
  • 候选函数:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。
  • 可行函数:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数
  • 寻找最佳匹配:基本思想:实参类型和形参类型越接近,它们匹配地越好。

调用重载函数应尽量避免强制类型转换。

🎨6.6.1 实参类型转换

实参类型到形参类型的转换分为几个等级,具体排序如下:

  1. 精确匹配
    实参类型和形参类型相同
    从数组类型或函数类型转化为对应的指针类型
    向实参中添加顶层const或删除顶层const
  2. 通过const转换实现的匹配。
  3. 通过类型提升实现的匹配
  4. 通过算术类型转换或指针转换实现的匹配
  5. 通过类类型转换实现的匹配

注意:所有算数类型转换的级别都一样。

🎯6.7 函数指针

  • 函数指针指向的是函数而非对象
  • 一种函数指针只能指向一种特定的函数类型:
bool Compare(const string&, const string&);  // 此函数的类型是 bool(const string&, const string&);
bool (\*pf)(const string&, const string&);    // 声明了一个指向 bool(const string&, const string&) 类型函数的指针,注意括号不能少;
pf = Compare;                                // 给指针赋值,指向 Compare 函数
pf = &Compare;                               // 这两种赋值语句完全等价
bool b1 = pf("hello","goodbye");             // 可以直接使用指针替代函数名调用函数。
bool b2 = (\*pf)("hello","goodbye");          // 与上面的等价
bool b3 = Compare("hello","goodbye")         //与上面等价

  • 当把函数名作为一个值使用时,函数自动地转换成指针
  • 不同函数类型的指针间不能相互转换。函数指针也可以指向 nullptr 或 0
  • 对于重载函数,指针类型必须与重载函数中的某一个精确比配。
string::size_type str(cosnt string&,const strint&)
bool str1(const char\*, const char\*)
pf = 0;
pf = str;      //错误,返回类型不匹配
pf = str1;     //错误,形参类型不匹配
pf = Compare;  //正确,函数和指针类型精确匹配

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

nullptr 或 0

  • 对于重载函数,指针类型必须与重载函数中的某一个精确比配。
string::size_type str(cosnt string&,const strint&)
bool str1(const char\*, const char\*)
pf = 0;
pf = str;      //错误,返回类型不匹配
pf = str1;     //错误,形参类型不匹配
pf = Compare;  //正确,函数和指针类型精确匹配

[外链图片转存中…(img-wIrVfPpt-1715798399261)]
[外链图片转存中…(img-I7URV1jG-1715798399262)]
[外链图片转存中…(img-5LnSylrl-1715798399262)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值