写在前面:
- 本系列专栏主要介绍C++的相关知识,思路以下面的参考链接教程为主,大部分笔记也出自该教程,笔者的原创部分主要在示例代码的注释部分。
- 除了参考下面的链接教程以外,笔者还参考了其它的一些C++教材(比如计算机二级教材和C语言教材),笔者认为重要的部分大多都会用粗体标注(未被标注出的部分可能全是重点,可根据相关部分的示例代码量和注释量判断,或者根据实际经验判断)。
- 如有错漏欢迎指出。
参考教程:黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili
一、函数的定义
1、概述
(1)函数是一个可以独立完成某个功能的语句块,其主要作用是将复杂程序拆成若干易于实现的子程序。
(2)在C++中,函数分为标准函数(又称为预定义函数)和用户自定义函数。
2、函数的定义形式
<返回类型> <函数名>(<形参列表>)
{
<函数体>
}
(1)函数名一般是标识符,最好能反映函数的功能。
(2)形参列表由逗号分隔,分别说明函数的各个形参,形参将在函数被调用时从调用函数那里获得数据(也就是把实参的数据拷贝到形参中)。形参列表可以为空,但是括号不能省略。
(3)返回类型又称函数类型,表示一个函数所计算(或运行)的结果值的类型,如果一个函数没有结果值,如函数仅用来更新(或设置)变量值、显示信息等,则该函数返回类型为void类型。
①return; ②return <表达式>;
①在返回类型为void的函数体中,若想跳出函数体,将执行流程转移到调用该函数的位置,应使用return语句的第一种格式(也就是return后面不带表达式的一种)。
②在返回类型不是void的函数体中,应使用(应该是必须使用)return语句的第二种格式(也就是return后面带表达式的一种),使执行流程转移到调用该函数的位置,并将<表达式>的值作为函数的返回值。
二、函数的调用
1、概述
(1)C++中函数调用的一般形式为:
<函数名>(<实参表>)
(2)当调用一个函数时,其实参的个数、类型及排列次序必须与函数定义时的形参相一致,也就是实参与形参应该一对一地匹配。若函数定义时没有形参,则函数调用时实参表亦为空(括号不能省略)。
2、语句调用
语句调用通常用于不带返回值的函数。
cpp
#include<iostream>
using namespace std;
void print_message(const char * msg)
{
cout << msg << endl;
return;
}
int main() {
print_message("Hello, world!"); //语句调用
//""Hello, world!""在这里的作用是"Hello, world!"字符数组的首元素地址,该字符串常量存储在内存四区的全局区中
system("pause");
return 0;
}
3、表达式调用
将被调用函数作为表达式的一部分进行调用,适用于被调用函数带有返回值的情况。
cpp
#include<iostream>
using namespace std;
int max(int a, int b)
{
return a > b ? a : b;
}
int main() {
cout << max(10 ,20) << endl; //表达式调用
//10和20称为实际参数,调用函数时,实际参数的值会传递给形式参数
system("pause");
return 0;
}
4、参数调用
被调用函数作为另一个函数的一个参数进行调用。
cpp
#include<iostream>
using namespace std;
int max(int a, int b)
{
return a > b ? a : b;
}
int main() {
cout << max(10, max(100, 30)) << endl; //表达式调用+参数调用
system("pause");
return 0;
}
三、函数的声明
1、概述
(1)在C++中,函数在使用之前要预先声明,这种声明在标准C++中称为函数原型,函数原型给出了函数名、返回类型以及在调用函数时必须提供的参数的个数和类型。
(2)函数原型的语法为:
<返回类型> <函数名>(<形参列表>); //注意要有分号
2、两种声明形式
(1)直接使用函数定义的头部,并在后面加上一个分号。
(2)在函数原型声明中省略参数列表中的形参变量名,仅给出函数名、函数类型、参数个数及次序。
cpp
#include<iostream>
using namespace std;
//代码是从上往下进行编译的
//int max(ine a, int b); 可以用这一行声明告诉计算机有这个被定义的函数,这样max函数的定义可以挪到主函数下面
int max(int , int );
int max(int a, int b); //声明可以有多次,定义只能有一次
int main()
{
int num1 = 10;
int num2 = 20;
cout << max(num1, num2) << endl;
system("pause");
return 0;
}
int max(int a, int b) //倘若函数的实现在main函数之上,那么可以不需要对该函数进行声明
{
return a > b ? a : b;
}
四、函数返回类型
1、四类函数的定义形式
(1)带参数的有返回值函数:
<返回类型> <函数名>(<形参列表>)
{
<语句序列>
return <表达式>;
}
(2)不带参数的有返回值函数:
<返回类型> <函数名>( )
{
<语句序列>
return <表达式>;
}
(3)带参数的无返回值函数:
void <函数名>(<形参列表>)
{
<语句序列>
return; //如果这是最后一条语句,那么可有可无
}
(4)不带参数的无返回值函数:
void <函数名>( )
{
<语句序列>
return; //如果这是最后一条语句,那么可有可无
}
2、举例
cpp
#include<iostream>
using namespace std;
//无参无返
void test01()
{
cout << "这是第一个测试" << endl;
}
//有参无返
void test02(int a)
{
cout << "这是第二个测试" << a << endl;
}
//无参有返
int test03()
{
int c = 1000;
cout << "这是第三个测试" << endl;
return c;
}
//有参有返
int test04(int d)
{
cout << "这是第四个测试" << d << endl;
return d;
}
int main() {
test01(); //第一个函数的调用
int b = 0;
test02(b); //第二个函数的调用
int num1 = test03(); //第三个函数的调用
cout << "num1 = " << num1 << endl;
int num2 = test04(b); //第四个函数的调用
cout << "num2 = " << num2 << endl;
system("pause");
return 0;
}
五、函数参数
1、参数的传递方式
(1)值传递:将实参值的副本传递(复制)给被调用的形参,函数运作过程中不会也不能影响实参。
cpp
#include<iostream>
using namespace std;
//实现两个数字进行交换的函数
void swap(int num1, int num2) //函数不需要返回值时,声明写void
{
int temp = 0;
cout << "交换前:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
temp = num1;
num1 = num2;
num2 = temp;
cout << "交换后:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
//不需要返回值时,不写return
}
int main() {
int a = 1;
int b = 10;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
//当我们做值传递的时候,函数的形参发生改变,并不会影响实参
swap(a, b); //调用函数进行两数交换
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
(2)地址传递:参数类型是指针,将实参值的指针副本传递(复制)给被调用的形参,函数运作过程通过对指针解引用可以访问实参,进而可以影响实参。
cpp
#include<iostream>
using namespace std;
//值传递
void swap1(int a, int b)
{
int temp = 1;
a = b;
b = temp;
}
//地址传递
void swap2(int *p1, int *p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main() {
int a = 10;
int b = 20;
swap1(a, b);
cout << a << " " << b << endl;
swap2(&a, &b); //如果是地址传递,可以改变实参
cout << a << " " << b << endl;
system("pause");
return 0;
}
(3)引用传递:
①函数传参时,可以利用引用的技术让形参修饰实参,这样可以简化指针修改实参。
②通过引用参数产生的效果同按地址传递是一样的,而且引用的语法更清楚简单。
③通过引用传递函数参数属于隐式传地址(传指针属于显式传递地址),当参数声明为引用,在函数调用时,编译器会自动将实参的地址传给被调用函数。
cpp
#include<iostream>
using namespace std;
//1、值传递
void mySwap01(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
//2、地址传递
void mySwap02(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//3、引用传递
void mySwap03(int &a, int &b) //引用传递,形参也会修饰实参
{
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
mySwap01(a, b);
cout << "a=" << a << " b=" << b << endl;
mySwap02(&a, &b);
cout << "a=" << a << " b=" << b << endl;
mySwap03(a, b);
cout << "a=" << a << " b=" << b << endl;
system("pause");
return 0;
}
④传递地址可以节省复制大量数据所需的内存空间和时间。
⑤如果不希望实参被函数修改,可以在函数形参列表中const修饰形参以防止形参改变实参。
cpp
#include<iostream>
using namespace std;
//常量引用使用场景:用于修饰形参,防止误操作
void showValue(const int& val)
{
//val = 1000; 如果不加const,万一在函数体内val发生变化,由于是引用传递,主函数中的a也会发生变化
cout << val << endl;
}
int main() {
int a = 100;
int &ref = a; //引用必须引一块合法的内存空间
const int & ref2 = 10; //加上const之后,编译器将代码修改为int temp =10 ; const int & ref2 = temp ;
//ref2 = 20; 加入const之后变为只读,不可修改
showValue(a);
system("pause");
return 0;
}
2、默认参数
(1)在C++中,可以为形参指定默认值,在函数调用时没有指定与形参相对于的实参时就自动使用默认值。
(2)默认参数通常在函数名第一次出现在程序中的时候指定,如在函数定义时的参数列表中指定默认参数,从语法上看指定默认参数和变量初始化类似。
(3)如果一个参数中有多个参数,则默认参数应从右至左逐个定义(如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值)。
cpp
#include<iostream>
using namespace std;
int func(int a,int b = 20,int c = 30) //函数默认参数
{
return a + b + c;
}
//如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
//如果函数声明有默认参数,函数实现就不能有默认参数(二者最多只能有一者有默认参数)
/*
int fun(int a = 10, int b = 10);
int fun(int a = 10, int b = 10) 这一行不应该再写"= 10"了,否则会出现"二义"
{
;
}
*/
int main() {
cout << func(12) << endl;
cout << func(12, 30) << endl; //如果我们自己传入数据,就用自己的数据,否则用默认值
system("pause");
return 0;
}
3、占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置。
cpp
#include<iostream>
using namespace std;
void func(int a,int) //函数占位参数,占位参数也可以有默认参数(int a,int = 10)
{
cout << "this is func " << a << endl;
}
int main() {
func(10,20); //目前阶段的占位参数暂时还用不到
system("pause");
return 0;
}
六、函数重载
1、函数重载概述
(1)在C语言中,编写函数时必须确保函数名唯一,如果能用同一个函数名字在不同类型上做相类似的操作就会方便很多,这种情况即为函数重载,C++是支持这种情况的。
(2)函数重载需要满足的条件:
①同一个作用域下。
②函数名称相同。
③函数参数类型不同或者个数不同或者顺序不同。(如果函数参数列表除开参数名称外完全一致,那么即使返回值类型不同也不满足重载条件)
(3)举例:
cpp
#include<iostream>
using namespace std;
//函数重载可以让函数名相同,提高复用性
//函数重载需要函数都在同一个作用域下
//函数参数类型不同 或者 个数不同 或者 顺序不同
void func() //有了void func()后,就不能有int func(),函数返回值不可以作为函数重载条件
{
cout << "func 的调用" << endl;
}
void func(int a)
{
cout << "func (int a) 的调用" << endl;
}
void func(char a)
{
cout << "func (char a) 的调用" << endl;
}
void func(int a, double b)
{
cout << "func (int a, double b) 的调用" << endl;
}
void func(double a, int b)
{
cout << "func (double a, int b) 的调用" << endl;
}//五个函数全在全局作用域
int main() {
func(10);
func();
func('a');
func(3, 3.14);
func(3.14, 3);
system("pause");
return 0;
}
2、函数重载注意事项
(1)引用也可作为重载条件。
(2)当函数重载遇上默认参数,调用函数时需慎重。
cpp
#include<iostream>
using namespace std;
void func()
{
cout << "func() 的调用" << endl;
}
void func(int &a)
{
cout << "func (int &a) 的调用" << endl;
}
void func(const int &a)
{
cout << "const func (int &a) 的调用" << endl;
}
void func2(int a)
{
cout << "func2(int a) 的调用" << endl;
}
void func2(int a,int b = 10)
{
cout << "func2(int a,int b =10) 的调用" << endl;
}
int main() {
int a = 10;
func(a);
func();
func(10);
func2(10, 20);
//func2(10); 两个func2函数都能调用,出现二义性
system("pause");
return 0;
}
七、内联函数
1、概述
(1)C++引入内联函数的原因是用它来取代C语言中的预处理宏函数。
(2)内联函数和宏函数的区别在于,宏函数是由预处理器对宏进行替换,而内联函数是通过编译器实现的,因此内联函数是真正的函数,只是在调用的时候,内联函数像宏函数一样展开,所以它没有一般函数的参数压栈和退栈操作,减少了调用开销,执行效率比普通函数高。
2、预处理宏函数与内联函数
(1)预处理宏函数:(这里仅做简单介绍,具体可看C语言的教程)
①#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
#define name( parament-list ) stuff
[1]其中的parament-list(参数列表)是一个由逗号隔开的符号表,它们可能出现在stuff中。
[2]参数列表的左括号必须与name(函数名)紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
②举例:
cpp
#include<iostream>
using namespace std;
#define SQUARE(x) ((x) * (x)) //整个表达式以及每个参数最好都加括号
int main()
{
int a = 5;
printf("%d\n", 10 * SQUARE(a + 1));
return 0;
}
(2)内联函数:
①在C++中使用inline关键字来定义内联函数,inline关键字放在函数定义(声明)中函数类型之前。
②内联函数的使用方式和一般函数一样,只不过在程序执行时并不产生实际函数调用,而是在函数调用处将函数代码展开执行。
③编译器会将在类的说明部分定义的任何函数都认定为内联函数,即使它们没有用inline说明(在类和对象的一章会详细介绍)。
④内联函数也有一定的局限性,就是函数中的执行代码不能太多,结构也不能太杂,否则编译器将会放弃内联方式而采用普通的方式调用函数(不会报错)。
⑤举例:
cpp
#include<iostream>
using namespace std;
inline int square(int a)
{
return a * a;
}
int main()
{
int a = 5;
cout << square(a) << endl;
return 0;
}
八、递归函数
1、概述
(1)如果一个函数在其函数体内直接或间接地调用了自己,该函数就称为递归函数。
(2)使用递归需要注意以下几点:
①用递归编写代码往往较为间接,但一般要牺牲一定的效率,因为系统处理递归函数时都是通过压栈/退栈的方式实现的。
②无论哪种递归调用,都必须有递归出口,即结束递归调用的条件。
③编写递归函数时需要进行递归分析,既要保证正确使用了递归语句,还要保证完成了相应的操作。
2、举例
(1)代码:
cpp
#include<iostream>
using namespace std;
int fib(int n)
{
if (n > 1)
{
return fib(n - 1) + fib(n - 2);
}
if (n == 1 || n == 0) //递归出口
{
return 1;
}
}
int main()
{
int n;
cin >> n;
cout << fib(n) << endl;
return 0;
}
//在使用 fib 这个函数的时候,如果要计算第50个斐波那契数字,会特别耗费时间
//这时可以将递归改为非递归,防止出现栈溢出的现象,同时效率也会提高
(2)递归流程图:
九、变量的生存周期
1、两种生存周期
(1)变量由编译程序在编译时给其分配存储空间(称为静态存储分配),并在程序执行过程中始终存在,这类变量的生存周期与程序的运行周期相同,当程序运行时,该变量的生存周期随即存在,程序运行结束,变量的生存周期随即终止。
(2)变量由程序在运行时自动给其分配存储空间(称为自动存储分配),这类变量为函数(或块)中定义的自动变量,它们在程序执行到该函数(或块)时被创建,在函数(或块)执行结束时释放所占用的空间。
2、变量作用域
(1)在C++中,当标识符的作用域发生重叠时,在一个函数(或块)中声明的标识符可以屏蔽函数(或块)外声明的标识符或全局标识符。
(2)由于作用域的屏蔽效应,如果函数中有同名变量,则不能访问外部变量,在这种情况下,要想在函数内部访问函数外定义的变量,可以使用C++中的作用域运算符"::"。
十、函数的分文件编写
(1)分文件编写代码可以让代码结构更加清晰。
(2)函数分文件编写一般有4个步骤:
①创建后缀名为.h的头文件。
②创建后缀名为.cpp的源文件。
③在头文件中写函数的声明。
④在源文件中写函数的定义。(主函数也写在源文件中)
(3)举例: