C++入门:三、函数

这是我学习C++的第三篇笔记,函数。我的学习路径是

1. 变量和数据类型
2. 流程控制
3. 函数
4. 面向对象
5. 标准库

现在学习的是函数的声明、定义、调用等相关知识。

函数声明和定义

函数的声明包含返回类型,函数名字,0个或者多个形参,无函数体,通常在头文件中对函数进行声明。

返回类型 函数名称(参数类型1 参数1, 参数类型2 参数2);
// 例如声明一个求阶乘的函数
int fact(int val);

函数的定义包含返回类型,函数名字,0个或多个形参,以及函数体。

返回类型 函数名称(参数类型1 参数1, 参数类型2 参数2) {
    函数体
}

比如写一个求阶乘的函数,可以写成下面这样

int fact(int val)
{
    int ret = 1;
    while (val > 1) {
        ret *= val--; // ret乘val的值返回给ret,val再自减1
    }
    return ret;
}

写一些简单的函数大多数语言都差不多,不过可惜每种语言或多或少都有自己的特色,这是比较令人头秃的地方。

函数的参数

函数可以带有0或多个参数,每个参数都需要声明类型。参数传递可以传值和传引用。

如果形参是引用类型,那么它将绑定到对应的实参中,我们成为传引用。否则,将会把实参的值拷贝后赋值给形参,我们成为传值

传值调用的时候,函数内改变传递的变量,函数外该变量不会发生变化。例如

void passbyval(int val)
{
    val = 10;
    std::cout << val << std::endl;
}

int main() {
    int num = 1;
    std::cout << num << std::endl;
    passbyval(num);
    std::cout << num << std::endl;
    return 0;
}
// 输出
1
10
1

可以看到,参数传值,在函数内改变参数的值,不影响原值。

传引用的时候,函数内改变参数值相应会改原值,还是上面的例子,把参数改成引用试试


void passbyval(int &val)
{
    val = 10;
    std::cout << val << std::endl;
}

int main() {
    int num = 1;
    std::cout << num << std::endl;
    passbyval(num);
    std::cout << num << std::endl;
    return 0;
}
// 输出
1
10
10

可以看到,改变函数参数的值的时候原值也被更改。

C++中,建议使用引用形参代替指针,原因是拷贝大的容器或者变量效率低,并且有些类类型根本不支持拷贝,这时候智能通过引用传参。

指针作为参数

指针作为参数的时候传递的也是值,是指针的值。这个要怎么理解呢?可以看看下面的例子

// 传指针
void passptr(int *val)
{
    // 打印指针的地址
    std::cout << "函数内指针的地址:" << &val << std::endl;
    // 打印指针的值
    std::cout << "函数内指针的值:" <<  val << std::endl;

}

int main() {
    int num = 1;
    int *ptr = &num;
    std::cout << "函数外指针的地址:" << &ptr << std::endl;
    std::cout << "函数外指针的值:" << ptr << std::endl;
    passptr(&num);
    return 0;
}

// 输出
函数外指针的地址:0x7ffeec520760
函数外指针的值:0x7ffeec520768
函数内指针的地址:0x7ffeec520738
函数内指针的值:0x7ffeec520768

根据上面的运行结果可以看出,函数外指针的地址和函数内指针的地址不是一个地址,但是值是一样的,说明指针被复制了。因此传指针实际上传递的也是指针的值。

const 形参

const作为作为参数是很常见的做法。由于const不可变的特点,当使用const参数的时候,参数会非常安全(它不会被修改)。

参数中传入引用的目的很多时候是为了修改这个值,甚至把这个值当作返回值来用。因此,如果一个引用参数没有加const,给调用者隐含的意思就是这个值我会去修改它,它将是一个返回值。

数组参数

数组有个特殊的性质,不允许直接拷贝。因此我们不能将数组直接作为参数传递,当数组作为参数的时候,实际上需要传递的是数组的首元素的指针

void print(const int*)
void print(const int[]) //函数意图是传递一个数组参数
void print(const int[10])

上面函数声明虽然不同,但是实际上表达的意思都是一样的,当编译器处理这个函数调用的时候,只检查传入的参数是不是const int*。

如果我们传入的是一个数组,则会自动转换成数组第一个元素的指针。可以看看下面的代码

#include <iostream>

void print(int *arr) {
    std::cout << arr << std::endl;
}

int main() {
    int i = 0;
    print(&i); // 输出i的地址

    int arr[2] = {1,2};
    print(arr); // 输出首元素的地址
    return 0;
}

有默认值的参数

有些时候我们需要为函数定义多个参数,但大部分场景我们只需要用到其中的少部分参数,少部分场景才会用到所有的参数。比如文章标题过长,我们需要截断以便在设备商更好地显示,通常我们会定义一个默认的长度,只有特殊的情况才会去自定义长度。

// 带有固定参数
using std::string
string subtitle(string title, size_t pos = 0, size_t n = 10)
{
    return title.substr(pos, n);
}
string t = subtitle("hello world"); // t 为hello worl

函数的返回值

大多数类型都可以作为函数的返回类型,一种特殊的返回类型是void,表示函数不返回值。函数的返回类型不能是数组类型或者函数类型。

引用返回左值

如果函数返回的是一个引用,那么这个函数是一个左值,否则函数是一个右值。以下面代码为例

// 假设这里的idx是一个有效的下标
int &set(int arr[], int idx) {
    return a[idx];
}

int main() {
    int a[2] = {1,2};
    set(a, 1) = 10;
    std::cout << a[1] << std::endl;
    return 0;
}

// 将会输出 10

函数重载

如果同一个作用域内的几个函数名字相同但是行参不同,我们称为重载函数。比如

void print(int i)
{
    std::cout << "int: " <<  i << std::endl;
}

void print(double i)
{
    std::cout << "double: " << i << std::endl;
}

print(1);
print(1.122);

// 输出
// int: 1
// double: 1.122

函数重载能减少我们起名成本,不过给函数起不同的名字能让程序仍容易被人理解。

函数指针

当一个指针指向一个函数的时候,我们称这个指针为函数指针。其实和其他的指针一样,需要指向特定类型,函数指针指向的是函数类型,函数类型由返回值和参数类型共同决定,和函数名无关

因此如果想要声明一个指向函数的指针,只需要将指针名字替换函数名字即可。以求阶乘的函数为例子

int fact(int val);

// 声明一个可以指向fact函数的指针(括号是不可少的)
int (*pf)(int val) 

// 通常,声明指向函数的指针不写形参名,因此写成下面这样
int (*pf)(int)

如何使用

// 直接将函数名赋值给指向函数的指针
pf = fact;
// 调用
pf(3)

// 还可以这样赋值
pf = &fact; // 和pf=fact是等价的

你可能还喜欢下面这些文章

Go入门:三、函数的声明和调用

这是我Go学习笔记的第三篇!接下来学习的是Go的函数声明和调用。我的语言学习过程一般分为下面几个:1. 变量和数据类型2. 流程控制方法3. 函数声明和调用4. 面向对象5. 语言特性6. 标准库函数声明func 函数名称(参数表) 返回值类型 { // 函数体}写一个函数是非常简单的,掌握语法格式就可以了。函数是一个功能的封装,能让函数体内的代码得到很好的复用。比如我要输出个人信息,我可以把个人信息封装到函数里面,后续直接调用这个函数而不是每次都print一堆信息了上面定义的函数没有参数,也没有返回值,非常简单的一个函数。如果我想让姓名可变,那么可以定义一个带有参数的函数接下来定义一个有

python学习笔记:三、函数

这是第三篇python学习笔记,我们即将要学习python的函数。内容主要包括两个部分,函数的声明和函数的调用。函数声明和调用比如我们要声明一个“吃”的函数,语法如下:def eat(): return "eat something"print(eat())上面是一个没有参数的函数,做的事情很简单,声明一个函数,然后返回一个字符串。接下来要增加一个参数了。def ead(food): return "eat %s" % foodprint(eat('fruit'))可以看到,上面声明了一个带有一个参数的函数,当然可以声明带两个,三个等。这些都是固定的,那么如果要声明一个不固定参数的

Go语言的 make 和 new

new 和 make 是两个内置函数,主要用来创建并分配类型的内存。在我们定义变量的时候,可能会觉得有点迷惑,不知道应该使用哪个函数来声明变量,其实他们的规则很简单,new 只分配内存,make 只能用于 slice、map 和 channel 的初始化。下面我们就来具体介绍一下new在Go语言中,new 函数描述如下:从上面的代码可以看出,new 函数只接受一个参数,这个参数是一个类型,并且返回一个指向该类型内存地址的指针。同时 new 函数会把分配的内存置为零,也就是类型的零值。【示例】使用 new 函数为变量分配内存空间。当然,new 函数不仅仅能够为系统默认的数据类型,分配空间,自定义

C++实现python字符串的endswith方法

可以使用的或方法配合比较运算符来模拟方法的功能。下面是一个示例函数,它检查一个字符串是否以另一个字符串结束:在这个示例中,函数接受两个参数:和。函数首先检查的长度是否大于或等于的长度。如果不是,那么显然不能以结束,函数返回。否则,函数使用方法从的末尾提取与长度相同的子字符串,并将其与进行比较。如果它们相等,那么以结束,函数返回。否则,函数返回。请注意,这个函数是区分大小写的。如果你想要一个不区分大小写的版本,你可以在比较之前使用和函数将和转换为小写。在这个版本中,函数首先使用和函数将和转换为小写。然后,它调用函数来检查转换后的字符串是否以结束。

shell中 $? $# $* $$ $* $@ $0 特殊变量含义

shell中有一些常用的难记的特殊变量,如下:$0当前脚本的文件名$n传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。$#传递给脚本或函数的参数个数。$*传递给脚本或函数的所有参数。$@传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同$?上个命令的退出状态,或函数的返回值。$$当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。$0 - 当前脚本文件名$n - 第n个参数$# - 参数个数$* - 所有参数$@ - 所有参数$? - 上一个命令的返回值$$ - 当前进程id

C++动态内存管理

C++中,动态内存管理是通过一对运算符来完成:new 和 delete。new操作符在内存中为对象分配空间并返回一个指向该对象的指针,delete接收一个动态对象的指针,销毁该对象,并释放与之相关的内存。手动管理内存看起来只有这两个操作,似乎很轻松,但实际上这是一件非常繁琐的事情,分配了内存但没有释放内存的场景发生的概率太大了!回想一下,你有多少次打开抽屉却没关上,拿出来的护肤品擦完脸之后却忘了放回去,吃完饭却忘了洗碗。类似这种没有收尾的事情我做的太多了。(以上这些都是在实际生活中我爱人批评我的点)我连这种明面上的事情都能忘记收尾,何况分配内存!所以为了世界和平,我放弃了手动管理内存。好在C+

C++ 判断 char* 是否相等

在C++中, 是一个指向字符的指针,通常用于表示C风格的字符串。判断两个  指针是否相等,需考虑两个方面:判断指针本身的地址是否相等:可以通过直接使用  或  操作符来实现。 和  指向不同的地址(尽管它们的内容相同),而  和  指向相同的地址。2. 判断指针指向的字符串内容是否相等:需要使用  函数,它是C标准库中的一部分,在C++中可用。 函数用于比较 、 和  的内容。如果内容相同, 返回

C++ any容器的介绍与简易实现

一、any容器是什么?1、any“不是”模板类,any是一种很特殊的容器。2、any只能容纳一个元素,但这个元素可以是任意的类型,可以是基本数据类型(int、double、string、标准容器或者任何自定义类型)。3、一种动态(类型检查只发生在运行时)语言特性的数据结构。4、C++17引入,需要RIIT支持,VS默认是没有支持C++17的,需要自己修改设置,如果不能使用any,请修改标准。二、any类摘要C++typeid关键字详解:三、any类用法注意:any的析构函数删除内部holder对象。如果类型是指针,any并不会对指针执行delete操作,所有any保存原始指针对造成内存泄漏。完

signal函数详解

signal作用是为信号注册一个处理器。这里的“信号”是软中断信号,这种信号来源主要有三种:程序错误:比如除0,非法内存访问。外部信号:终端Ctrl-C产生的SIGINT信号,定时器产生的SIGALERM。显示请求:kill函数发送的任意信号。当kill一个进程的时候,默认会发送SIGTERM信号,此时这个信号只有默认处理操作(SIG_DFL),直接中断进程执行。如果此时该进程正在执行一个任务,直接终止该进程会导致任务没有完成。这个时候为SIGTERM信号注册一个信号处理函数就十分有必要。介绍参数sig要设置信号处理函数的信号。它可以是实现定义值或下例值之一:SIGABRTSIGFPESIGI

布隆过滤器(bloom filter)介绍以及php和redis实现布隆过滤器实现方法

引言在介绍布隆过滤器之前我们首先引入几个场景。场景一在一个高并发的计数系统中,如果一个key没有计数,此时我们应该返回0。但是访问的key不存在,相当于每次访问缓存都不起作用了。那么如何避免频繁访问数量为0的key而导致的缓存被击穿?有人说, 将这个key的值置为0存入缓存不就行了吗?这是确实是一种解决方案。当访问一个不存在的key的时候,设置一个带有过期时间的标志,然后放入缓存。不过这样做的缺点也很明显:浪费内存和无法抵御随机key攻击。场景二在一个黑名单系统中,我们需要设置很多黑名单内容。比如一个邮件系统,我们需要设置黑名单用户,当判断垃圾邮件的时候,要怎么去做。比如爬虫系统,我们要记录下

赞赏

微信赞赏支付宝赞赏

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注