函数的命名规则与变量相同。
调用时指定的实参传递信息。
函数
函数的结构
函数头
return_type function_name (parameter_list)
- 返回值类型
- 函数名
- 函数形参
函数体
{}内的代码块
形参和函数体内声明的所有变量都是该函数的局部变量,返回值是生成的副本。
同名局部变量会屏蔽全局变量。
#include "pch.h"
#include <iostream>
int a = 99;
int main()
{
int a = 88;
std::cout << a;
}
输出88
return语句
return expression
expression 的结果必须是函数头中为返回值指定类型。
没有返回值的return可以省略。
拖尾返回类型
函数的返回类型放在函数头中->的后面,auto关键字告诉编译器返回类型在以后确定。
#include "pch.h"
#include <iostream>
auto calc(int a, int b)->double
{
return static_cast<double>(a)/b;
}
int main()
{
std::cout.precision(4);
std::cout<< calc(1, 2)<<std::endl;
std::cout<< std::fixed << calc(1, 2) << std::endl;
}
使用函数
如果函数定义没有出现在相同源文件的前面,就必须使用函数原型语句来声明函数。
函数原型:函数原型指定要传递给函数的形参、函数名、返回值类型,声明时最后跟一个分号。
函数原型和函数定义中函数头中的形参名称可以不同,甚至可以省略函数名,但类型和数量必须对应。
#include "pch.h"
#include <iostream>
double calc(int, int); //声明函数
int main()
{
std::cout.precision(4);
std::cout<< calc(1, 2)<<std::endl;
std::cout<< std::fixed << calc(1, 2) << std::endl;
}
auto calc(int a, int b)->double
{
return static_cast<double>(a) / b;
}
给函数传递实参
按值传递机制
按值传递机制中,指定的实参变量、常量或表达式的值并没有传递给函数,而是创建这些实参的副本,再传递给函数。
给函数传递指针实参
当使用指针作为实参时,按值传递机制一样工作。
#include <iostream>
using namespace std;
int ChangeValue(int*, int); //声明函数
int main()
{
int num{ 0 };
int num1{ 10 };
int* pnum{ &num };
cout << num << endl; //显示实参值
cout << ChangeValue(pnum, num1) << endl; //返回副本值
cout << num << endl; //显示改变后实参值
}
int ChangeValue(int* ptra, int b)
{
*ptra = b; //修改的是副本的值
return *ptra;
}
OutPut:
num: 0
ChangeValue(pnum, num1): 10
num: 10
给函数传递数组
数组是唯一不能按值传递的类型。(函数体内能改变参数(源数组)的值)
编译器将数组名转换为指针,指向数组头部的指针副本通过按值传递机制被传递给函数。
#include <iostream>
using namespace std;
void ChangeValue(int[], int); //声明函数
int main()
{
int a[]{ 1,2,3,4,5 };
cout << typeid(a).name() << endl;//a类型是数组
for (int item: a)
{
cout << item << endl;
}
ChangeValue(a, sizeof(a)/ sizeof(a[0]));
cout << "Call ChangeValue(a, sizeof(a)/ sizeof(a[0]))" << endl;
for (int item : a)
{
cout << item << endl;
}
}
void ChangeValue(int arr[], int count)
{
cout << typeid(arr).name() << endl; //arr类型是指针
for (int i = 0; i < count; i++)
{
arr[i] += 10;
}
}
Output:
int [5]
1
2
3
4
5
int * __ptr64
Call ChangeValue(a, sizeof(a)/ sizeof(a[0]))
11
12
13
14
15
数组没有记录其大小信息,所以不能作为实参传递给函数的数组使用基于范围的for循环。
给函数传递多维数组
当定义多维数组作为形参时,可以省略第一维度值。
int add(int arr[][4],int count)
#include <iostream>
using namespace std;
void ChangeValue(int[][5], int, int); //声明函数
int main()
{
int a[][5]{ { 1,2,3,4,5 } ,{ 21,22,23,24,25 } };
cout << typeid(a).name() << endl;//a类型是数组
for (int i = 0; i < _countof(a); i++)
{
for (int j = 0; j < _countof(a[0]); j++)
{
cout << a[i][j] << " ";
}
cout << endl;
}
cout << endl;
ChangeValue(a, _countof(a), _countof(a[0]));
cout << "Call ChangeValue(a, _countof(a), _countof(a[0]))" << endl;
for (int i = 0; i < _countof(a); i++)
{
for (int j = 0; j < _countof(a[0]); j++)
{
cout << a[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
void ChangeValue(int arr[][5], int count1, int count2)
{
cout << typeid(arr).name() << endl; //arr类型是指针
for (int i = 0; i < count1; i++)
{
for (int j = 0; j < count2; j++)
{
arr[i][j] += 2;
}
}
}
Output:
int [2][5]
1 2 3 4 5
21 22 23 24 25
int (* __ptr64)[5]
Call ChangeValue(a, _countof(a), _countof(a[0]))
3 4 5 6 7
23 24 25 26 27
给函数传递引用实参
将函数的某个形参指定为引用,将改变该形参传递数据的方法。该机制不再赋值提供的实参,允许函数直接访问调用实参。
#include "pch.h"
#include <iostream>
using namespace std;
void ChangeValue(int&, int); //声明函数
int main()
{
int num{ 0 };
int num1{ 10 };
cout << num << endl; //显示实参值
ChangeValue(num, num1);
cout << "ChangeValue(num, num1)" << endl; //返回副本值
cout << num << endl; //显示改变后实参值
}
void ChangeValue(int& re_num, int b)
{
re_num = b; //修改的是副本的值
}
output:
0
ChangeValue(num, num1)
10
不能对lvalue引用形参对应的实参使用表达式,除非此表达式是一个lvalue。
lvalue引用形参对应的实参使用表达式,必然会导致某些数据永久地存储在内存中。
rvalue引用类型的形参允许传递到函数的表达式是rvalue。
使用const修饰符
const 修饰形参,阻止程序修改实参。
#include <iostream>
using namespace std;
void ChangeValue(const int&, int); //声明函数
int main()
{
const int num{ 0 };
int num1{ 10 };
cout << num << endl; //显示实参值
ChangeValue(num, num1);
cout << "ChangeValue(num, num1)" << endl; //返回副本值
cout << num << endl; //显示改变后实参值
}
void ChangeValue(const int& re_num, int b)
{
re_num = b; //会报错!
}
rvalue引用形参
rvalue引用形参可以引用一个rvalue(表达式的临时结果),但rvalue引用形参本身并不是一个rvalue,是一个lvalue。
#include <iostream>
using namespace std;
void ChangeValue(int&&, int); //声明函数
int main()
{
int num{ 0 };
int num1{ 10 };
ChangeValue(num + num1, num1);
cout << "ChangeValue(num + num1, num1)" << endl; //返回副本值
cout << num << endl; //显示改变后实参值
}
void ChangeValue(int&& re_num, int b)
{
re_num += b;
}
main函数的实参
可以将main函数定义成不带任何形参,也可以指定一个参数列表。
int main(int argc,char* argv[])
{
}
第一个形参时命令行上出现的,包括程序名在内的字符串数量。argc至少是1,至少要输入程序名。
第二个形参是一个数组,它包含指向这些字符串的指针,还有一个为空值的附加元素。
接收参数不定的函数实参
可以将函数定义成能够接收任何数量的实参。将省略号(...)写在函数定义中形参列表的最后,即可表示调用函数时可以提供可变的实参。
int add(int count,...) //一般传递一个可变参数数量的普通参数
{
}
函数中至少要有一个普通形参。
c++库在cstdarg头文件中定义了va_start、va_arg和va_end宏,帮助我们获取可变实参。
//va_list 用来依次指向各个可变实参
//va_arg 返回arg_ptr指向的实参值,并使指针递增
type va_arg(
va_list arg_ptr, //arg_ptr 指向参数列表的指针。
type //type 要检索的参数类型。
); //va_arg 返回当前参数。
//va_copy、va_start、和 va_end 不返回值。
//复制可变参数
void va_copy(
va_list dest, //复制源va_list ,指向要从 src 中初始化的参数列表的指针
va_list src //复制目标va_list,指向要复制到 dest 的参数初始化列表的指针。
); // (ISO C99 and later)
//将arg_ptr置空
void va_end(
va_list arg_ptr //arg_ptr 指向参数列表的指针。
);
//使其指向可变参数列表的第一个值(第一个实参)
void va_start(
va_list arg_ptr, //arg_ptr 指向参数列表的指针。
prev_param //位于第一个可选实参之前的形参。即可变形参前面的普通形参。
); // (ANSI C89 and later)
void va_start(
arg_ptr
); // (deprecated Pre-ANSI C89 standardization version)
在 中定义的 C 标准 STDARG.H
宏按如下方式使用:
-
va_start
将arg_ptr
设置为传递到此函数的参数列表中的第一个可选参数。 参数arg_ptr
必须拥有va_list
类型。 参数prev_param
是紧跟在参数列表中的第一个可选参数之前的必需参数的名称。 如果prev_param
使用注册存储类声明,则不定义宏的行为。 在首次使用va_arg
前必须使用va_start
。 -
va_arg
从arg_ptr
指定的位置中检索type
的值,并增加arg_ptr
以通过使用type
的大小来确定下一个参数的开始位置,来指向列表中的下一个参数。 可以在函数中使用va_arg
任意次,以检索列表中的参数。 -
va_copy
复制当前转态下的参数列表。src
参数必须已使用va_start
进行初始化;可以使用va_arg
调用进行过更新,但是不能使用va_end
进行过重置。va_arg
从dest
中检索到的下一个参数与从src
中检索到的下一个参数相同。 -
检索所有参数后,重置
va_end
指向 的指针NULL
。 在函数返回前必须在已使用va_start
或va_copy
初始化的各个参数列表上调用va_end
。
从函数返回值
返回指针
return_type* function_name(parameter)
{
//返回地址的规格:永远不要从函数中返回局部自动变量(局部作用域变量)的地址
return_type* variable;//不推荐。
...
return variable; //注意返回类型必须同函数头中的返回类型一致
}
//函数外
return_type* ptr {function_name(parameter)};
delete ptr ;
ptr = nullptr;
返回地址的规格:永远不要从函数中返回局部自动变量(局部作用域变量)的地址。
#include <iostream>
double* treble(double);
int main()
{
double num{ 5.0 };
double* ptr{};
ptr = treble(num);
std::cout << "3.0*num: " << num * 3.0 << std::endl;
std::cout << "Result : " << *ptr << std::endl;
}
double* treble(double data)
{
double result{ }; //在函数返回时销毁
result = 3.0 * data;
return &result; //销毁后指针指向的变量不再包含原来的值。
}
输出:
3.0*num: 15
Result : -9.25596e+61
正确的方法可以使用动态内存分配。
#include "pch.h"
#include <iostream>
double* treble(double);
int main()
{
double num{5.0};
double* ptr{};
ptr = treble(num);
std::cout << "3.0*num: " << num*3.0 << std::endl;
std::cout << "Result : " << *ptr << std::endl;
}
double* treble(double data)
{
double* result{ new double{} };
*result = 3.0 * data;
return result; //该变量一致存在,直到用delete销毁。此处没有删除,内存会逐渐消耗。
}
输出:
3.0*num: 15
Result : 15
使用delete释放内存
#include "pch.h"
#include <iostream>
double* treble(double);
int main()
{
double num{5.0};
double* ptr{};
ptr = treble(num);
std::cout << "3.0*num: " << num*3.0 << std::endl;
std::cout << "Result : " << *ptr << std::endl;
delete ptr; //释放内存
ptr = nullptr;
}
double* treble(double data)
{
double* result{ new double{} };
*result = 3.0 * data;
return result;
}
输出:
3.0*num: 15
Result : 15
返回引用
可以从函数中返回一个lvalue的引用。
lvalue引用不能独自存在(它总是其他对象的别名),所以必须确保其引用的对象在函数执行完之后仍然存在。
从函数返回lvalve引用意味着可以在赋值语句的左边使用函数的结果。
返回引用的规则:永远不要从函数中返回对局部变量的引用。
#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;
using std::setw;
double& lowest(double values[], int length); // Function prototype
int main()
{
double data[]{ 3.0, 10.0, 1.5, 15.0, 2.7, 23.0,
4.5, 12.0, 6.8, 13.5, 2.1, 14.0 };
int len{ _countof(data) }; //
for (auto value : data)
cout << setw(6) << value;
lowest(data, len) = 6.9; // 返回引用将允许在赋值语句的左边使用函数。
lowest(data, len) = 7.9; // 返回引用将允许在赋值语句的左边使用函数。
cout << endl;
for (auto value : data)
cout << setw(6) << value;
}
// 返回引用的函数
double& lowest(double a[], int len) //& lvalue的申明
{
int j{}; // 最低元素的索引
for (int i{ 1 }; i < len; i++)
if (a[j] > a[i]) // 找出小值
j = i;
return a[j]; // 返回对最低元素的引用。
//不要混淆返回&a[j]与返回引用。&a[j]指定的是a[j]的地址,那是一个指针。
}
Output:
3 10 1.5 15 2.7 23 4.5 12 6.8 13.5 2.1 14
3 10 6.9 15 2.7 23 4.5 12 6.8 13.5 7.9 14
函数中的静态变量
可以在函数内部将某个变量声明为static。
static int count {};
函数内静态变量的初始化仅发生在第一次调用该函数的时候。
事实上,初次调用函数时将创建并初始化静态变量。之后该变量在程序执行期间将继续存在,退出函数时该变量包含的任何值都可以在下次调用函数时使用。
#include <iostream>
#include <iomanip>
int increment();
int main()
{
for (int i = 0; i < 10; i++)
{
std::cout << increment() << std::endl;
}
}
int increment()
{
static int a {0}; //静态变量,仅在函数第一次调用时初始化,下次调用时继续存在
return ++a;
}
Output:
1
2
3
4
5
6
7
8
9
10
递归函数
除非遇到的问题特别适用于使用递归函数,或者没有明显的代替方法,否则使用其他方法一般会更好。
使用循环比使用递归的函数调用效率更好。
#include "pch.h"
#include <iostream>
#include <iomanip>
int factorial(int);
int main()
{
std::cout << factorial(5) << std::endl;
}
int factorial(int a)
{
if (a >= 1)
return factorial(a - 1) * a;
if (a == 1)
return 1;
}
函数指针
指向函数的指针必须包含想调用的函数的内存地址,还必须包含被指向函数的形参列表以及返回类型等信息。
声明函数指针
return_type (*pointer_name)(list_of_parameter_types)
- 指向函数的返回类型return_type
- 指针名称pointer_name(注意括号内包含*和函数指针名)
- 指向函数的形参类型list_of_parameter_types
这种指针只能指向具有声明中指定的return_type和list_of_parameter_types的函数。如果视图赋给指针一个与指针声明中的类型不相符的函数,编译器将生成一条错误消息。
long sum(long num1,long num2); //函数原型
long (*pfun)(long,long){sum}; //函数指针指向函数sum(声明函数指针并赋值)
auto pfun2 = sum; //可以使用auto关键字
#include <iostream>
#include <iomanip>
using namespace std;
int add(int, int);
int mul(int, int);
int main()
{
int (*funptr)(int, int) {};
funptr = add;
cout << funptr(3, 4) << endl;
funptr = mul;
cout << funptr(3, 4) << endl;
}
int add(int a, int b)
{
return a + b;
}
int mul(int a, int b)
{
return a * b;
}
输出:
7
12
函数指针作为实参
#include <iostream>
#include <iomanip>
using namespace std;
int add(int, int);
int mul(int, int, int (*funptr)(int, int));//int (*funptr)(int, int) 函数指针
int main()
{
int (*funptr)(int, int) {}; //指针作为函数的形参
funptr = add;
cout << mul(3, 4, add) << endl;
}
int add(int a, int b)
{
return a + b;
}
int mul(int a, int b, int (*funptr)(int, int)) //指针作为函数的形参
{
funptr(a, b);
cout << funptr(3, 4) << endl;
return a * b;
}
Output:
7
12
函数指针的数组
用常规指针相同的方式来声明函数指针的数组。
return_type (*pointer_name[size]) ( list_of_parameter_types )
return_type (*pointer_name[]) (list_of_parameter_types) { function_list };
//函数声明
double sum(cosnt double,const double);
double product(cosnt double,const double);
double difference(cosnt double,const double);
//申明指针数组并初始化
//不能用auto推测出数组的类型,必须明确指定类型
double (*pfun[]) (cosnt double,const double) {sum,product,difference};
//使用指针数组调用函数
pfun[1](2.5,2.5);
初始化函数形参
函数指定默认的形参值,如果在调用该函数时省略了实参,就使用其默认值。
应该将拥有默认值的参数依次全部放在函数原型中形参列表的最后,使最可能被省略的参数最后出现。
#include <iostream>
#include <iomanip>
using namespace std;
void show(const char s[] = "default value"); //函数声明(函数原型)时指定默认值
int main()
{
show();
show("new value");
}
void show(const char s[]) //函数定义
{
cout << s << endl;
}
Output:
default value
new value
异常
- try:标志可能出现异常的代码块,后面必须紧跟至少一个catch块
- throw: 使异常状态产生,并抛出特定类型的异常
- catch:标志处理异常的代码块。try后面可以跟几个catch块。
抛出异常
try代码块中的任何位置都可以使用throw语句抛出异常,throw语句的操作数决定着异常的类型。
捕获异常
如果希望使某个catch代码块处理try代码块中抛出的任何异常,则必须将省略号(...)放入包围异常声明的圆括号内。
重新抛出异常
使用没有操作数的throw,会重新排除要处理的异常。
MFC中的异常处理
MFC函数通常抛出的异常是特定的类类型(CDBException)。
如果抛出的异常是CDBException类型,则作为catch代码块形参出现的类型应该是CDBException*。如果没有重新抛出该异常,就还必须在catch块中调用其Delete()函数,删除异常对象。
不应使用delete删除异常对象,因为该对象可能没有在堆上分配内存。
CDBException是所有MFC异常的基类,所以这里的catch块会捕获任何类型的MFC异常。
处理内存分配错误
#include<new> // For bad_alloc type
#include<iostream>
using std::bad_alloc;
using std::cout;
using std::endl;
int main()
{
char* pdata{};
size_t count{ ~static_cast<size_t>(0) / 2 }; //取反0,所有位是1,整数最高位是0,表示正数,所以/2
try
{
pdata = new char[count];
cout << "Memory allocated." << endl;
}
catch (const bad_alloc& ex) //捕获异常
{
cout << "Memory allocation failed." << endl
<< "The information from the exception object is: "
<< ex.what() << endl;
}
delete[] pdata;
return 0;
}
new操作符抛出的异常时std::bad_alloc类型。bad_alloc是new标准头文件中定义的类类型。