异常 exception
string at()
string s = "hello";
s.at(10) == out_of_range
new/delete
new char[-54] == bad_alloc
我们在编写程序的过程中,会碰到各种错误
如,未声明、未定义、重定义、模糊不清、 语法错误
如,段错误、核心已转储... 异常
通常把 编译时就能检测出来的这些错误,称为语法错误
而把那些 程序执行时出现的各种错误,称为异常。
C++提供了新的错误处理机制:异常处理
异常处理的基本思想是把错误检测与错误处理分离开来。
异常处理机制主要包括:
1、throw表达式
2、try{}catch(){}语句
3、一套异常类型
throw表达式,异常检测部分使用throw表达式来表示它遇到了无法处理的问题,
一般称为throw引发或抛出了异常。
例:
void foo(int i)
{
if (i < 0)
{
throw 1; // 抛出了一个int类型的异常,异常值为1
}
cout << "i = " << i << endl;
}
throw语句后 所有的代码都不会再执行,同时在当前函数中查找异常处理代码,
如果当前函数中没有进得异常处理,throw会自动把 异常抛出给 当前函数的调用者,重复此过程,直到被处理。
如果异常最终抛给了 *** 作系统, *** 作系统会自动调用terminate函数,终止进程。
try语句块
一般语法格式:
try
{
可能发生异常的代码块
}
catch(异常类型1 形参)
{
处理异常
}
catch(异常类型n 形参)
{
处理异常
}
catch(...)
{
处理任意异常
}
说明:
try语句块中的代码可能发生异常,如果没有发生异常,则catch语句不起作用
如果发生了异常,异常发生点 后面的代码不会再执行,程序流程进入catch语句
catch用来捕获异常,根据异常类型进行匹配,异常类型匹配时不会发生类型转换。
catch语句可以有多个
如果不在意抛出的异常类型,可以使用 ... 来表示 捕获任意类型的异常,...这种写法只能出现在catch语句的最后面。
例:
int main()
{
try
{
foo(-100);// 异常发生点
foo(100); // 此行代码不会执行。
}
catch(int e)
{
cerr << "捕获到int类型的异常" << endl;
...
}
catch(...)
{
cerr << "捕获到其他类型的异常" << endl;
...
}
}
标准异常类
C++标准库提供了一组异常类型,用来说明 库函数 中遇到了问题,这些异常类型可以程序中直接使用
这些异常类型定义于 头文件
常用异常类型:
std::logic_error
std::runtime_error
std::out_of_range
std::bad_alloc
这组异常类型都提供了一个成员函数 what(), 此函数返回一个const char*类型的错误信息。 例:
void foo(int i)
{
if (i < 0)
throw std::logic_error("i < 0");
}int main()
{
try
{
foo(-1);
}
catch(std::logic_error e)
{
cerr << e.what() << endl;
}
}
应用场合
析构函数 不能抛出异常,析构函数的声明中默认包括关键字 noexcept (noexcept表示该函数不会抛出异常,如果抛出了异常,程序终止)
构造函数 中如果遇到了无法解决的问题,推荐使用异常
其他函数,根据需要选择即可。
运算符重载 (本质是函数重载)
C++中的运算符可以作用于基本类型(如int,double等)、标准库类型(如string)及类类型的对象。
基本类型 与 标准库中的类型的运算符功能已经由 语言及标准库实现了。
而自定义的类型的运算符功能则 需要通过 运算符重载 来实现。
所谓 运算符重载,就是给已有的运算符 添加 新功能,使得运算符能作用于 类类型 的对象上。
如:可以使用cout输出整数、小数、字符串,也可以输出一个点、矩形等
class Point
{
public:
private:
int x;
int y;
};
Point pos;
cout << pos << endl;
C++中的运算符是用函数实现的,与其它的函数一样,重载的运算符函数也包括 返回类型、函数名、参数列表及函数体。
一般形式如下:
返回类型 operator运算符(参数列表);
说明:
重载的运算符是 具有特殊名字的 函数,由关键字operator和要重载的运算符组合而成。
例: 加法运算符重载函数名为 operator+
运算符函数既可以是 类的成员函数,也可以是 普通的全局函数
如果是 普通全局函数,则一元运算符函数有一个参数,二元运算符函数有两个参数
如果是 类的成员函数,则一元运算符没有参数,二元运算符函数有一个参数
运算符->运算符函数:
运算符 对应 函数名
*** 作数 对应 参数列表
*** 作结果 对应 函数返回值 典型的双目运算符:+ - * / == != > < <= >= 的重载方式类似,以+ 为例:
class Complex // 复数
{
public:Complex(r,i):real(r),imag(i) {}
private:
double real; // 实部
double imag; // 虚部
};
Complex operator+(Complex c1, Complex c2)
{
//Complex c3;
double real = c1.real + c2.real
double imag = c1.imag + c2.imag;
return Complex(real,imag);
}Complex c1(1,2);
Complex c2(2,3);
Complex c3 = c1 + c2; //c3 operator+(c1, c2)
输入输出运算符
输出运算符函数 一般如下:
std::ostream& operator<<(std::ostream& out, 类型名 形参);
ostream的参数与返回类型都必须为非const引用(ostream类没有拷贝构造函数,被删除了)
输入输出运算符必须重载为友元函数。不能重载为类的成员函数。
拷贝赋值运算符
如果类中没有显式的定义赋值运算符函数,编译器会自动生成,自动生成的赋值运算符执行浅拷贝。
如果一类拥有资源,则需要 自定义 析构函数、拷贝构造函数 和 赋值运算符函数
赋值运算符函数只能重载为类的成员函数
赋值运算符重载的基本步骤:
1、防止自赋值
2、释放原有资源
3、重新分配新的资源
4、返回自引用
例:
string& string::operator=(const string& rhs)
{
cout << "赋值运算符重载函数" << endl;
// 防止自赋值
if (this == &rhs)
return *this;
// 释放原有空间
delete [] _data;
// 重新分配资源
_data = new char[rhs.size()+1];
strcpy(_data, rhs._data);
// 返回自引用
return *this;
}
移动赋值运算符
与移动构造函数类似,用于移动对象的资源
一般的形式如下:
类名& operator=(类名&&);
移动赋值运算符实现:
1、移动资源
2、释放右侧对象对资源的控制权
例:
string& string::operator=(string&& rhs)
{
// 防止自赋值
if (this == &rhs)
return *this;// 释放原有空间
delete[] _data;// 资源转移
_data = rhs._data;
rhs._data = nullptr;// 返回自引用
return *this;
}
注:
C++标准库提供了一个move()函数,用来获取给定参数的右值引用
例:
int i = 10;
int && r = std::move(i);
静态成员+运算符重载 => 单例模式
单例模式:(这个唯一的实例一只只使用一个唯一的地址,只能一次一次的用,不能同时使用)
在程序执行过程中,一个类只能有一个实例。
在C++中的基本实现步骤:
1、隐藏构造函数
2、提供一个公有的静态成员函数,以创建那唯一的实例
3、删除拷贝构造函数、拷贝赋值运算符函数
例:
class Singleton
{
public:
void setValue(int x)
{
this->x = x;
}
int getValue() const
{
return x;
}
// 提供一个全局的接口,以获取唯一的那个实例
static Singleton& getInstance()
{
static Singleton s(0); // 实例化一个静态局部对象
return s;
}
Singleton(const Singleton&) = delete; // 删除拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 删除拷贝赋值运算符
private:
Singleton(int x):x(x) {} // 隐藏构造函数
int x;
};
int main()
{
Singleton& s = Singleton::getInstance();
s.setValue(100);
cout << s.getValue() << endl;
}
单目运算符
++
自增运算符既可以重载为 友元函数,也可以重载为 类的成员函数
自动运算符分两种:
前++
类名& operator++(类名&); // 友元函数
类名& operator++(); // 成员函数
后++
类名 operator++(类名&, int);
// 友元函数,多了一个int类型的参数,称为占位符参数,不参与运算
类名 operator++(int);// 成员函数,多了一个int类型的参数,称为占位符参数,不参与运算
例:
class Complex {};
Complex& operator++(Complex& c) //前++
{
++c.real;
++c.imag;
return c;
}Complex operator++(Complex& c, int) //后++
{
Complex t = c;
++c;
return t;
}int main()
{
Complex c(0,0);
Complex c1 = ++c;
cout << c << endl; // Complex(1,1)
cout << c1 << endl; // Complex(1,1)c = Complex(2,2);
Complex c2 = c++;
cout << c << endl; // Complex(3,3)
cout << c2 << endl; // Complex(2,2)
}
函数调用运算符 ()
如果一个类 重载了调用运算符,则该类型的对象,称为函数对象(仿函数)
例:
class Print
{
public:
void operator()(int i)
{
cout << "i = " << i << endl;
}
void operator()(std::string s)
{
cout << s << endl;
}
private:
...
};int main()
{
Print p;
p(100); // p是一个函数对象,可以调用p("hello");
}
自动类型转换
在C++中,一个表达式中,如果类型不匹配,编译器会尝试自动类型转换。
构造函数转换:
如果定义了一个构造函数,这个构造函数能把另一个类型的对象作为它的单参数,则这种构造函数允许编译器执行自动类型转换
例:
class Demo
{
public:
Demo(int i): i(i) {}
private:
int i;
};void foo(Demo d)
{}foo(100); // Demo d = 100; => Demo tmp(100); Demo d = tmp; Demo(100)
如果不希望发生这种隐式的类型转换,可以在单参数的构造函数前,加一个新的关键字 explicit
explicit 用来阻止编译器做隐式转换。
class Demo
{
public:
explicit Demo(int i): i(i) {}
private:
int i;
};void foo(Demo d)
{}foo(100); // error
foo(Demo(100)); // ok
运算符转换:
类型转换运算符 是类的一种特殊的成员函数,它负责将一个类类型的值转换成其它类型的值
一般形式:
operator type() const;
说明:
1、type 表示类型名,待转换的目标类型名,只要该类型能作为函数的返回类型(void除外)
2、这种函数没有显式的返回类型,也没有形参,它的返回类型就是type类型
3、必须是成员函数
4、通常不应该修改待转换的对象,所以要加 const 进行说明
例:
class Demo
{
public:
explicit Demo(int i): i(i) {}
operator int() const
{
return i;
}
private:
int i;
};Demo d(100);
int i = d; // 自动把d转成100
小结:
1、可以重载大多数运算符,但有些不能,如:
. ?: :: sizeof
2、只能重载已有运算符,不能创建新的运算符
3、重载的运算符应该要与它的原始语义保持一致,如加法运算中不能实现减,不能修改 *** 作数等
4、以下运算符只能重载为类的成员函数
= () [] ->
5、输入输出运算符只能重载为友元函数
6、只有当 *** 作数中至少有一个自定义类型时,才需要重载运算符。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)