0%

C++的语句与函数

try语句与异常处理

通常的异常处理包括三部分:

  • throw表达式:使用throw表达式表示当前程序遇到了某个问题(异常声明)
  • try语句块:与catch配合用于处理各类异常声明
  • 一套异常类:用于传递具体的异常信息(异常声明的含义)

例程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
#include<windows.h>
#include <stdexcept>
using std::vector;
using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::runtime_error;
int main()
{
int a,b;
cin>>a>>b;
try
{
if(a!=b) throw runtime_error("ERROR!!!");
cout<<"good"<<endl;
}
catch(runtime_error err)
{
cout<<err.what();
}
system("pause");
return 0;
}

throw表达式

1
if(a!=b) throw runtime_error("ERROR!!!");

格式:throw+异常类+初始化异常类对象

  • 该初始化的对象只用于提供一些关于异常的辅助信息(用户自行定义)

  • 异常类为一系列在库中定义的类型,使用时需要声明其命名空间,以下是几种常用的异常类,在头文件stdexcept中

    | exception | 一般错误 |
    | :——————-: | :————————————————————: |
    | runtime_error | 运行时才能检测出的任务 |
    | range_error | 运行时:生成结果超出有意义的值域范围的错误 |
    | overflow_error | 运行时:计算上溢错误 |
    | underflow_error | 运行时:计算下溢错误 |
    | logic_error | 程序逻辑错误 |
    | domain_error | 逻辑错误:参数对应的结果不存在 |
    | invalid_error | 逻辑错误:无效参数 |
    | length_error | 逻辑错误:超出对象类型长度 |
    | out_of_range | 逻辑错误:超出有效范围 |

    • 除exception外其他类型都必须用一个字符串初始化,而exception则不允许这种初始化

      1
      2
      3
      4
      5
      6
      7
      8
      9
      try
      {
      if(a!=b) throw exception();
      cout<<"good"<<endl;
      }
      catch(exception)
      {
      cout<<"ERROR!!";
      }

try语句块

格式:

1
2
3
4
5
6
try
{ }//程序正常逻辑,异常类在此throw出
catch(异常声明)
{}
catch(异常声明)
{}
  • 当try内一旦提出异常类时,就不再执行该throw表达式后的内容;反之,若没有throw任何异常,则不会触发任何catch语句,程序正常执行完try语句。
  • catch括号中需定义一个对象,类型为触发的异常类,这些异常类只有一个子函数what,用于输出初始化时的字符串(不能用于exception类)

当有多个try语句块嵌套时,当最内部try提出了一个异常,会由内到外执行catch,若最终都没有对应catch,则程序转到terminate的标准库函数,该函数行为与系统有关,通常执行该函数会导致程序非正常退出。

函数

参数传递

  • 形参实参之间的转递为值拷贝
  • C++中建议用引用类型的形参代替指针形参,引用类型同样可以达到修改值的内容,并且可以避免拷贝,因为引用是与实参绑定的。

数组的传递

以下三种对形参的定义是等价的:

1
2
3
void a(const int*);
void b(const int[]);
void c(const int[10]);

编译器在编译时只会检查传入的参数类型是否为const int*型,甚至不会检查传入数组的长度是否超出形参大小。

数组的引用形参

可以使用数组的引用作为形参:

1
2
3
4
5
void test(int (&arr)[10])
{
for(auto elem : arr)
cout<<elem<<endl;
};

使用数组的引用作为形参需要注意:

  • 初始化时必须使用括号括起&arr
  • 输入的实参必须为一个int [10]类型的对象

函数的返回类型

返回引用

函数返回引用可以理解为返回的为一个地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char test1(string s ,string::size_type num)
{
return s[num];
}
char &test2(string &s ,string::size_type num)
{
return s[num];
}

int main()
{
srting s("test");
cout<<test1(s,0)<<endl; //t
test2(s,0)='a';
cout<<s[0]<<endl; //a
return 0;
}

当一个函数的返回值为引用时,该返回值为左值而非右值,因此可以像使用其他左值一样使用这个返回值

(注:左值≠地址,它仍然是一个值)

  • 不可返回局部变量的引用,由于局部变量在函数完成后会被释放,因此在函数完成后引用会找不到它的对象。例如上一个例程,如果把test2()函数改为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    char &test2(string s ,string::size_type num)//此处形参不再是引用
    {
    return s[num];
    }
    int main()
    {
    srting s("test");
    test2(s,0)='a';
    cout<<s[0]<<endl; //t
    return 0;
    }

    该函数的返回值为对s的引用,但s本身为一个局部变量,在函数完成时已经被释放,因此该引用是无效的(但是这样返回形参的引用编译器并不会报错,而返回函数内定义的局部变量的引用会报错)

    但如果形参定义为引用,由于引用本身是不占用存储空间的,所以’a’可以通过引用直接向s[0]赋值

返回数组指针

  • 尾置返回类型

    1
    auto func(int i) -> int(*)[10]

    声明函数的返回值为一个含有十个整数的数组的指针

  • 使用decltype

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int test[5]={1,2,3,4,5};
    decltype(test) *func()
    {
    return &test;
    }
    int main()
    {
    cout<<*(*func+1); //输出2
    return 0;
    }
    • decltype(test)的结果是一个数组,因此函数前要用*声明返回值为指针
    • 使用此方法返回的是一个数组的整体地址,而不是首地址,类型为int*[5],因此一次解引用的结果才是首地址

内联函数

正常的调用函数一般比等价的表达式要慢(保存寄存器、拷贝等操作),因此对于规模较小的函数可以定义为内联函数类型:

1
2
3
4
inline int fuc(int a,int b)
{
return a+b;
}

constexpr函数

将函数隐式地定义为内联函数,但函数内只能有一条return语句,且返回值必须为字面值