C++程序结构

函数的命名规则与变量相同。

调用时指定的实参传递信息。

函数

函数的结构

函数头

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_typelist_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标准头文件中定义的类类型。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值