- 总集篇:C++ 总结 - CSDN
C++总结1 - C++基础
1.1 cin 用法
cin
是 C++ 的标准输入流对象,即istream 类
的对象,主要用于从标准输入(终端键盘)读取数据
从键盘输入字符串时需要按下回车键才能够将这个字符串送入到缓冲区中
则这个回车键\r
会被转换为一个换行符\n
,作为一个字符被存储在缓冲区中
cin
是从缓冲区中获取数据。缓冲区为空时,cin
的成员函数会阻塞,等待数据;当缓冲区中有数据,就触发cin
的成员函数去读取数据。
- cin
cin
从输入缓冲区读取数据时,会跳过首个有效字符之前的空格
Tab
换行
等分隔符,但cin
读取字符成功后,该字符之后的分隔符会残留在输入缓冲区。示例如下:
#include <iostream>
using namespace std;
int main() {
char a;
cout << "input:";
cin >> a;
cout << a << endl;
}
输入: [space][Tab][Enter]s[Enter]
输出: s
第一个有效字符s前的[space][Tab][Enter]都被跳过了
-
getline()
- 函数原型:
istream& getline (istream& is, string& str);
- 函数介绍:
Extracts characters from is and stores them into str until the newline character ‘\n’.
getline()用于捕获[换行]'\n'
符,并将换行符之前的内容全都保存在str中,并且直接从输入缓冲区中将’\n’删除
- 函数原型:
-
cin.getline()
cin.getline()
与getline()
类似,cin.getline()
的输出是char*
,getline()
的输出是string
,所以 cin.getline() 属于 istream 流,而 getline() 属于 string 流,这两个函数不一样
-
cin.get()
- 用于获取输入缓冲区的第一个字符,不会忽略任何分隔符。读取成功后该字符之后的数据继续残留在输入缓冲区。
-
cpp 1:多组数据的输入输出 A+B Problem
#include <iostream>
#include <cstdio>
using namespace std;
// 解法1: 若没有数据,cin >>会返回false,终止循环
int main()
{
int a, b;
while (cin >> a >> b) {
cout << a + b << endl;
}
return 0;
}
//解法2: 利用 C语言中scanf()返回值来判断是否成功读入一组数据
int main()
{
int a, b;
while (scanf("%d%d", &a, &b) == 2) {
cout << a + b << endl;
}
return 0;
}
1.2 const 用法
- const在 * 左边,变量为const,如
int* const d;
或int *const d;
- const在 * 右边,指针为const,如
const int *c;
const int a; a为const,定义一个常整数型变量
int const b; b为const,定义一个常整数型变量
const int *c; c为const,定义一个指向长整型数的指针(所指向的内存数据不能修改,但本身可以修改)
int* const d; int*为const,定义一个常量指针(指针变量不能被修改,但是它所指向内存空间可以被修改)
const int* const e; int*为const; e为const,指向常整型的常指针(指针和它所指向的内存空间都不能被修改)
const int func1(); func1和func2中的const都修饰的是返回值,函数返回一个常整型数(const int)
int const func2();
int func3(int x)const; const放在参数列表后,表示只读取参数列表中的变量,不改变参数列表中的任何参数
int func4(const int x, int y); const放在参数列表中修饰参数,表示只读取x,不改变x; 而对y能做读写操作
void func(int x,int y) const{}
该语句只能在定义类的成员函数的时候使用
这种写法的本质是: void fun (const 类名 *this, int x, int y);
const修饰的不是形参 x 和 y; const修饰的是属性 this -> x 和 this -> y
因为c++对类的this指针做了隐藏,本质上,const修饰的是this指针
- 合理使用const的好处
- 可以有效的提高代码的可读性,减少bug.
- 清楚的分清参数的输入和输出特性(是否能修改)
- const修饰的对象仅能调用 const 成员函数,而不能调用非const修饰的函数
/* 上述好处3的示例说明: */
class Test {
public:
int a;
void fun_normal(){}
void fun_const() const{}
};
int main() {
Test test;
const Test& testA = test;
cout << testA.a << endl; //true,能访问公有成员a
testA.fun_normal(); //wrong,对象包含与成员函数不兼容的类型限定符,只能访问const修饰的成员函数
testA.fun_const(); //true
return 0;
}
1.3 static 用法(暂无)
暂无
1.4 new 用法
在使用关键字new
在堆上动态创建一个对象时,它实际上做了三件事:获取一块内存空间
、调用构造函数
、返回指向已获取内存的指针
。(如果创建的是简单类型的变量,第二步省略)。
用new
申请的动态内存空间一定要用delete
运算符进行释放
delete释放数组时,语句为delete[] 指针名;
new操作失败时抛出的特殊异常是 bad_alloc
- 在C++中提到new,可能表示这三种含义:
new operator
、operator new
、placement new
- new operator 就是平时经常使用的关键字new。 new operator 的第一步分配内存实际上是通过调用
operator new
来完成的,operator new
实际上是像加减乘除一样的操作符,也可以重载。operator new
默认情况下首先调用分配内存的代码,尝试获取一块堆上的空间,如果成功就返回,如果失败,则会去调用new_hander
,然后继续重复上述过程 - 若对该过程不满意,可通过重载operator new来实现我们希望的行为。示例如下:
- new operator 就是平时经常使用的关键字new。 new operator 的第一步分配内存实际上是通过调用
class A{
public:
void* operator new(size_t size) {
printf("operator new called/n");
return ::operator new(size);
} //::operator new调用了原有的全局的 new
}; //实现了在分配内存之前输出提示信息的行为
A* a = new A();
- 全局的operator new也可以重载的,但就不能再递归地使用new来分配内存,而只能使用malloc,示例如下
void* operator new(size_t size) {
printf("global new/n");
return malloc(size);
}
1.5 auto用法
关键字auto
用来声明自动变量。它是存储类型标识符,表明变量(自动)具有本地范围。块范围的变量声明(如for循环体内的变量声明)默认为auto存储类型。其实大多普通声明方式声明的变量都是auto变量,不需要明确指定auto关键字,默认就是auto的了。auto变量在离开作用域是会变程序自动释放,不会发生内存溢出情况(除了包含指针的类)。使用 auto变量的优势是不需要考虑去变量是否被释放,比较安全。
- 与
new
关键字的对比:new是用来在堆上申请内存地址的关键字,他产生的变量不会自动释放,除非delete来手动释放,或者程序结束时由操作系统释放,使用new的优势是内存使用比较灵活,理论可以申请任意大小的内存区块(实际与操作系统有关),但这很容易产生问题,一不小心忘记释放对象,特别是在频繁调用的函数内创建的对象忘记释放时,会产生内存溢出,严重时导致程序出错,系统崩溃。new一般都是在类的定义中使用,结合delete可以使包含new出来对象的类也具有自带变量功能,这样就继承了两种方式的优势。 - 最新的C++标准更新了auto关键字的功能除了具有原有的含义外,还增加了一种类似其他高级语言的型别推导特性使用auto来代替变量的类型,前提是被明确类型的初始化变量初始化的,可以使用auto关键字比如
int i = 10; auto a = i;
这样a也是int类型了这在使用一些模板类的时候,对于减少冗赘的代码也很有用
简单示例:
void autoValue() //auto变量
{
auto age = 1;
auto name = '%';
auto height = 12.8f;
auto weight = 10.24;
cout << "age's type is " << typeid(age).name() << endl;
cout << "name's type is " << typeid(name).name() << endl;
cout << "height's type is " << typeid(height).name() << endl;
cout << "weight's type is " << typeid(weight).name() << endl;
//typeid是C++中的关键字,输出结果与平台有关
//typeid(变量).name()表示 变量的类型
}
void autoPointer() //auto指针
{
auto age = new int(1); //age类型为 int*
auto name = "Who am I?"; //name的类型为 const char*
auto height = new float(12.8f);
auto weight = new double(20.48);
cout << "age's type is " << typeid(age).name() << endl;
cout << "name's type is " << typeid(name).name() << endl;
cout << "height's type is " << typeid(height).name() << endl;
cout << "weight's type is " << typeid(weight).name() << endl;
}
void autoPass() {
auto x = 1;
auto y = x;
auto z = 2.56;
z = x;
cout << "x's type is " << typeid(x).name() << endl;
cout << "y's type is " << typeid(y).name() << endl;
cout << "z's type is " << typeid(z).name() << endl;
}
运行结果:
age's type is int
name's type is char
height's type is float
weight's type is double //end autoValue
age's type is int *
name's type is char const *
height's type is float *
weight's type is double * //end autoPointer
x's type is int
y's type is int
z's type is double //end autoPass
关于for(auto &i : s)
和for(auto i : s)
的使用示例:
string s = "hello,world";
for (auto i : s) //i相当于s[i]的传值变量,改变i时不会对s[i]产生影响
i = toupper(i); //toupper函数能将小写字母变成大写字母
cout << s << endl; //输出结果: hello,world
for (auto &i : s) //i是s[i]的引用,改变i时会改变s[i]的值
i = toupper(i);
cout << s << endl; //输出结果: HELLO,WORLD
1.6 引用
引用相当于给变量起"别名"
1.6.1 引用的使用
- 引用作函数的返回值
相当于直接把放在函数setValue()的位置上
int n = 4;
int & setValue() { return n; }
setValue() = 40;
cout << n; //Output: 40
** 重载[]运算符 **
class vector {
int *v; int size;
public:
vector(int n) { v = new int[n]; size = n; }
~vector() { delete []v; size = 0; }
int& operator[](int i) { return v[i]; }
};
int main() {
vector a(5); a[2] = 12;
cout << a[2] << endl; //Output: 12
- 引用 作函数的参数
- cpp 2:引用 解决swap Problem
#include <iostream>
using namespace std;
/* Swap Error */
void swapError(int a, int b) {
int tmp;
tmp = a; a = b; b = tmp;
}
int n1 = 3, n2 = 5;
swapError(n1, n2); //输出3, 5 | n1,n2的值不会被交换
/* Swap 1 : pointer and address */
void swap1(int* a, int* b) {
int tmp;
tmp = *a; *a = *b; *b = tmp;
}
int n1 = 3, n2 = 5;
swap1(&n1, &n2); //输出5, 3
/* Swap 2 : the reference to the class A */
class A {
public:
int x;
int getX() { return x; }
};
void swap2(A& a, A& b) {
int tmp = a.x; a.x = b.x; b.x = tmp;
}
A a, b; a.x = 3; b.x = 5;
swap2(a, b);
}
/* Swap 3 : the reference to the pointer */
void swap3(int* & a,int* & b) {
int* tmp = a; a = b; b = tmp;
}
int a = 3, b = 5;
int* pa = &a; int* pb = &b;
swap3(pa, pb);
1.6.2 常引用(暂无)
1.6.3 引用和指针的区别(暂无)
1.7 函数重载
1.7.1 普通函数的重载
实际编程开发中,有时需要实现一些功能相似的函数,仅仅是部分细节不同。例如上例 cpp 2 中swap()交换两个变量的值,这两个变量有多种类型,可以是 int、double、char、bool 等,需要通过不同类型的参数把变量的地址传入函数。如果在C语言中,往往需要分别设计出多个函数名不同的函数,其函数声明示例如下:
void swapInt(int* a, int* b);
void swapDouble(double* a, double* b);
void swapChar(char* a, char* b);
void swapBool(boolean* a, boolean* b); //boolean等同于 C/C++中的bool
但在C++中,允许多个函数拥有相同的函数名,只要这些函数的参数列表或返回值不同,这就是 函数的重载(Function Overloading) 。借助重载,一个函数名就能实现多种功能。(一定程度上也能防止,程序员因为绞尽脑汁想各种各样的函数名,而导致多掉头发,过早秃顶。变相地延长了程序员的“寿命”)函数重载示例求和函数sum()如下:
int sum(int a, int b) { return a + b; } //两个整型数相加
double sum(double a, double b) { return a + b; } //两个双精度浮点数相加
float sum(float a, float b) { return a + b; } //两个单精度浮点数相加
int sum(int a, int b, int c) { return a + b + c; } //三个整型数相加
函数重载的规则
- 函数名称相同
- 参数列表不同(个数不同、类型不同、不同类型的参数排列顺序不同等)
- 函数的返回类型可以相同或不相同(只有返回类型不同不足以成为函数的重载)
- 同名函数的功能应当相同或相近,不要用重载函数去实现完全不相关的功能,程序虽能运行,但可读性差(做项目的时候也容易挨揍)。
C++语言实现函数重载的方式:
C++代码在编译时会根据参数列表对函数进行重命名,例如 void Swap(int a, int b)
会被重命名为_Swap_int_int
, void Swap(float x, float y)
会被重命名为 _Swap_float_float
。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做 重载决议(Overload Resolution) 。
从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。此外,不同的编译器有不同的重命名方式,这里仅举例说明,实际情况可能并非如此。
1.7.2 运算符重载
关于+
-
*
=
++
[]
()
<<
>>
运算符的重载 和 复数、矩阵类、有理数运算
详见我的另一篇文章: C++ 运算符重载 - CSDN
1.8 inline 内联函数
在C语言中,如果一些函数被频繁调用,不断地有函数入栈(即函数栈),会造成栈空间或栈内存的大量消耗。为了解决这个问题,C++引入了inline
修饰符,表示为内联函数。
(栈空间就是指放置程式的局部数据也就是函数内数据的内存空间,在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,如函数的死循环递归调用的最终结果就是导致栈内存空间枯竭)
内联是以代码膨胀(复制)为代价,减少函数调用的时间开销,从而 提高函数的执行效率。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。编译器处理对内联函数的调用的语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
- 内联函数特点
- 提高运行速度
- 增加代码大小
//内联函数的定义
inline int Max(int a, int b) {
if (a > b) return a;
return b;
}
int main() {
int a = 3, b = 6, c = 4, max = 0;
max = Max(a, b);
max = Max(max, c);
cout << max << endl;
}
int main() { //两个main函数完全相同
int a = 3, b = 6, c = 4, max = 0;
if (a > b) max = a; //if-else结构等同于max = Max(a, b);
else max = b;
if(max > c) max = max; //if-else结构等同于max = Max(max, c);
else max = c;
cout << max << endl;
}
- 以下情况不宜使用内联:
- 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高
- 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大
1.9 模板
- C++中模板用于批量生成功能和形式都几乎相同的代码。有了模板,编译器就能在需要的时候,根据模板自动生成程序的代码。从同一个模板自动生成的代码,形式几乎是一样的。模板有效提高了程序的可重用性
1.9.1 函数模板
考虑求两个参数之中较大者的函数:max(a, b);
对于不同类型的a, b,都有相同的处理形式:a > b ? a : b
用已有方法解决对不同数据类型处理:
- 宏替换
#define max(a, b) (a > b ? a : b)
缺点:避开类型检查,容易出问题 - 函数重载
缺点:需要很多重载版本,很繁琐 - 函数模板
函数模板用于生成函数(这样的函数也称作模板函数)。函数模板不是一个实际存在的函数,编译器不能为其生成可执行代码。定义函数模板只是对函数功能框架的描述,当模板实例化时,将根据传递的参数决定其具体功能。
- 函数模板格式:
template < typename T1, typename T2, ... > // 模板说明
返回值类型 函数模板名(参数列表) //函数定义
{
函数体
}
- 模板格式说明:
template
是关键字,所有模板(包括类模板)均以template开头< typename T1, typename T2, ... >
是类型形式参数表,表中的类属参数必须在函数定义中至少要出现一次,typename
关键字也可用class
关键字替换T
是类属参数,代表某种数据类型。同一个类属参数只能替换为同一种类型。在模板自动生成函数时,编译器会根据实参的类型判断,并用具体的类型名替换模板中的类属参数T,其他部分保留不变。
- 函数模板使用注意事项:
- 当已有函数模板不能达到要求时,可以通过重载函数模板来实现更多功能
- 函数模板不提供隐式类型转换,示例详见cpp 4
- cpp 4 :Max(a, b) Problem
#include <iostream>
#include <string>
using namespace std;
template <typename T> *函数模板*
T Max(const T a, const T b)
{ return a > b ? a : b; }
template <class T> *重载函数模板*
T Max(const T a, const T b, const T c)
{ T t; t = Max(a, b); return Max(t, c); }
int Max(const int a, const char b) *用普通函数重载函数模板*
{ return a > b ? a : b; }
int main() {
cout << " Max(9.3, 0.5) is " << Max(9.3, 0.5) << endl;
cout << " Max(9, 5, 23) is " << Max(9, 5, 23) << endl;
cout << " Max( 3, 'a' ) is " << Max(3, 'a') << endl;
}
程序运行结果:
Max(9.3, 0.5) is 9.3
Max(9, 5, 23) is 23
Max( 3, 'a' ) is 97
1.9.2 类模板
- 类模板用于实现类所需数据的类型参数化
- 类模板在表示如数组、表、图等数据结构很重要,这些数据结构的表示和算法不受所包含的元素类型的影响
- 类模板格式:
template <typename T, ...> //模板说明
class 类名 { ... }; //类说明
类的声明中必须至少出现一次参数表中的类属参数
- cpp 5 :数组类模板
#include <iostream>
using namespace std;
template <class T> //类模板
class Array {
public:
Array(int s);
virtual ~Array();
virtual const T& Entry(int index) const;
virtual void Enter(int index, const T& value);
protected:
int size;
T* element;
};
//类模板的成员函数是函数模板
template <typename T>
Array<T>::Array(int s) { //构造函数
if (s > 1)size = s;
else size = 1;
element = new T[size];
}
template <typename T>
Array<T>::~Array() //析构函数
{ delete[] element; }
template <typename T>
const T& Array<T>::Entry(int index)const
{ return element[index]; }
template <typename T>
void Array<T>::Enter(int index, const T& value)
{ element[index] = value; }
int main() {
Array<int> IntAry(5); int i;
for (i = 0; i < 5; i++) IntAry.Enter(i, i);
cout << "Integer Array : \n";
for (i = 0; i < 5; i++) cout << IntAry.Entry(i) << "\t";
cout << endl;
Array<double>DouAry(5);
for (i = 0; i < 5; i++) DouAry.Enter(i, (i+1)*0.35);
cout << "Double Array : \n";
for (i = 0; i < 5; i++) cout << DouAry.Entry(i) << "\t";
cout << endl;
}
程序运行结果如下:
Integer Array :
0 1 2 3 4
Double Array :
0.35 0.7 1.05 1.4 1.75
- 类模板做函数参数
- 函数的形参类型可以是类模板或类模板的引用,对应的实际参数是该类模板的实例化模板类对象
- 当一个函数拥有类模板参数时,则它必定是函数模板
// 一个用Array<T>作参数的函数模板
template <typename T>
void Tfun(const Array<T>&x, int index)
{ cout << x.Entry(index) << endl; }
// 调用函数模板
Array <double> DouAry(5);
...
Tfun(DouAry, 3);
过程分析:
1. 生成模板类 -> 调用构造函数,实例化模板类,建立对象
2. 调用函数 -> 实例化为模板函数 -> 调用模板函数
- 类层次中的类模板
- 一个类模板在类层次结构中既可以是基类,也可以是派生类
- 类模板派生普通类,在定义派生类时要对基类的抽象类参数实例化
- 从普通类派生模板类,表示派生类增加了抽象类型数据成员 ?
- 类模板与友元
- 一个函数或函数模板可以是类或类模板的友元函数
- 一个类或类模板可以是类或类模板的友元类
- 类模板与static成员
- 从类模板实例化的每个模板类都有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
- 模板类的static数据成员应该在文件范围定义和初始化
- 每个模板类有自己的类模板的static数据成员副本
- 其他关于模板讲解:
1.10 vector
vector
(向量) 是一个封装了动态大小数组的顺序容器(Sequence Container)。跟任意其它类型容器一样,它能够存放各种类型的对象(如:int, string, 结构体, 指针等)。可以简单认为,vector是一个能够存放任意类型的动态数组。
- 需要头文件:
#include <vector>
- 创建vector对象:
vector <类型> 动态数组名
示例:vector< int >test; 建立一个vector,int为数组元素的数据类型,test为动态数组名 - vector的初始化:
vector<int> a(10); //定义了10个整型元素的向量(但没有给出初值,其值是不确定的)
vector<int> a(10, 1); //定义了10个整型元素的向量,且给出每个元素的初值为1
vector<int> a(b); //用b向量来创建a向量,整体复制型赋值
vector<int> a(b.begin(), b.begin+3); //定义了a值为b中第0个到第2个元素(共3个元素)
int b[7]={1,2,3,4,5,9,8}; vector<int> a(b, b+7); //从数组中获得初值
- vector对象的操作:
1. a.size(); //返回a中元素的个数;
2. a.assign(b.begin(), b.begin()+3); //b为向量,将b的0~2个元素构成的向量赋给a
3. a.assign(4,2); //是a只含4个元素,且每个元素为2
4. a.back(); //返回a的最后一个元素
5. a.front(); //返回a的第一个元素
6. a[i]; //返回a的第i个元素,当且仅当a[i]存在2013-12-07
7. a.clear(); //清空a中的元素
8. a.empty(); //判断a是否为空,空则返回ture,不空则返回false
9. a.pop_back(); //删除a向量的最后一个元素
10. a.erase(a.begin()+1,a.begin()+3); //删除a中第1个(从第0个算起)到第2个元素,也就是说删除的元素从a.begin()+1算起(包括它)一直到a.begin()+ 3(不包括它)
11. a.push_back(5); //在a的最后一个向量后插入一个元素,其值为5
12. a.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,如a为1,2,3,4,插入元素后为1,5,2,3,4
13. a.insert(a.begin()+1,3,5); //在a的第1个元素(从第0个算起)的位置插入3个数,其值都为5
14. a.insert(a.begin()+1,b+3,b+6); //b为数组,在a的第1个元素(从第0个算起)的位置插入b的第3个元素到第5个元素(不包括b+6),如b为1,2,3,4,5,9,8 ,插入元素后为1,4,5,9,2,3,4,5,9,8
15. a.capacity(); //返回a在内存中总共可以容纳的元素个数
16. a.resize(10); //将a的现有元素个数调至10个,多则删,少则补,其值随机
17. a.resize(10,2); //将a的现有元素个数调至10个,多则删,少则补,其值为2
18. a.reserve(100); //将a的容量(capacity)扩充至100,也就是说现在测试a.capacity();的时候返回值是100.这种操作只有在需要给a添加大量数据的时候才显得有意义,因为这将避免内存多次容量扩充操作(当a的容量不足时电脑会自动扩容,当然这必然降低性能)
19. a.swap(b); //b为向量,将a中的元素和b中的元素进行整体性交换
20. a==b; //b为向量,向量的比较操作还有!=,>=,<=,>,<
- vector使用部分示例:
1、通过下标方式读取数据
int a[5] = {1,2,3,4,5};
vector<int> v(a, a + 4);
for(int i = 0; i <= v.size()-1; i++)
cout << v[i] <<" ";
2、通过迭代器方式读取数据
int a[5] = {1,2,3,4,5};
vector<int> v(a, a + 4);
for(vector<int>::iterator it = v.begin(); it != v.end(); it++)
cout << *it << " ";
- vector使用时常见的误区
(1)Problem:
vector<int> a;
for (int i = 0; i < 10; i++) {
a[i] = i;
cout << a[i] << endl;
}
运行这段代码,编译器会报出严重错误!
错误原因: 赋值时,a还是个长度未定的vector空对象
或者说,下标只能用于获取已存在的元素(不一定已经初始化),而现在的a[i]还是空的对象
作出一点小修改(初始化时给动态数组定好长度):
vector<int> a(10);
for (int i = 0; i < 10; i++) {
a[i] = i;
cout << a[i] << " ";
}
运行结果: 0 1 2 3 4 5 6 7 8 9
1.11 异常处理 try catch throw
- cpp 4 : class NonNumber and try / catch / throw
编写NonNumber异常类,编写程序从键盘读入数字并存储为字符串,将该字符串转换为整数,在转换前进行测试:如果一个或多个字符不是数字,抛出NonNumber异常(一种情况除外:数字前面出现“-”)
#include <iostream>
#include <stdlib.h>
#include <string>
#include <sstream>
using namespace std;
class NonNumber {
public:
NonNumber() {
message = "INVALID INPUT: non-intager detected";
};
void what() { cout << message << endl; }
private:
string message;
};
int main() {
int num = 0;
string str;
stringstream trans;
cout << "Please enter a number(end-of-file to terminate):";
while(cin >> str)
{
try {
if (str.at(0) != '-' && (str.at(0) < '0' || str.at(0) > '9'))
throw NonNumber(); //抛出NonNumber异常
for (int i = 1; i < str.size(); i++) //包含第一个字符为'-'的情况,从第二个字符开始检查
if (str.at(i) < '0' || str.at(i) > '9')
throw NonNumber(); //抛出NonNumber异常
trans << str;
trans >> num;
cout << "The number entered was: " << num << endl;
trans.clear(); // 多次使用stringstream时需要
}
catch (NonNumber& n)
{
n.what();
}
cout << "Please enter a number(end-of-file to terminate):";
}
return 0;
}
还有个比较轻便的代码版本:
#include <iostream>
using namespace std;
int main()
{
try {
int a;
cin >> a;
if (cin.good()) cout << a << endl;
else throw "输入错误";
}
catch (const char* str) {
cout << str << endl;
}
return 0;
}
- cpp 4 :divideZero Problem(除0异常处理)
#include <iostream>
#include <string>
using namespace std;
class DivideZero {
string out;
public:
DivideZero() : out("EXCEPTION: Division by zero attempted.") {}
string display() const {
return out;
}
};
double arithmetic(int n, int d)
{
if (d == 0) throw DivideZero();
return static_cast< double >(n) / d;
}
int main()
{
try {
cout << arithmetic(24, 6) << endl;
cout << arithmetic(1, 3) << endl;
cout << arithmetic(9, 0) << endl;
}
catch (DivideZero& e) {
cout << e.display() << endl;
}
return 0;
}
运行结果如下:
4
0.333333
EXCEPTION: Division by zero attempted.
1.12 #ifndef #define #endif
假设项目里面有4个文件,分别是a.cpp,b.h,c.h,d.h
a.cpp 的头部: b.h和c.h的头部都是:
#include "b.h" #include "d.h"
#include "c.h"
而d.h里面有class D的定义
编译器编译 a.cpp 时,先根据 #include "b.h" 去编译b.h文件
再由b.h文件里的 #include "d.h",去编译d.h的这个文件
这样就把d.h里的 class D 编译
然后由 a.cpp 的第二句 #include "c.h",去编译c.h文件
最终还是会找到的d.h里面的 class D,但 class D 已经编译过了
所以这时编译器就会报出 *重定义错误*
编译顺序图示如下:
加上 #ifndef
/ #define
/ #endif
,即可防止这种重定义错误
#ifndef
ifndef 是 if not define 的简写,是宏定义的一种,实际上是预处理功能(宏定义、文件包含、条件编译)中的条件编译。
-
在c语言中,对同一个变量或者函数进行多次声明是不会报错的。所以如果.h文件里只是进行了声明工作,即使不使用 #ifndef 宏定义,多个.c文件包含同一个.h文件也不会报错
-
在c++语言中,
#ifdef
的作用域只是在单个文件中。所以如果.h文件里定义了全局变量,即使采用 #ifdef 宏定义,多个.c文件包含同一个.h文件还是会出现全局变量重定义错误。
使用 #ifndef 可以避免下面这种错误:如果在h文件中定义了全局变量,一个.c文件包含同一个.h文件多次,如果不加 #ifndef 宏定义,会出现变量重复定义的错误;如果加了 #ifndef,则不会出现这种错误
示例:
#ifndef x //先测试x是否被宏定义过
#define x
语句块1 //如果x没有被宏定义过,定义x,并编译语句块1
#endif
语句块2 //如果x已经定义过,则编译程序段2的语句,跳过语句块1
条件指示符 #ifndef 的最主要目的是防止因头文件被重复包含而导致多次编译,(头文件被包含多次很可能没办法避免,只是为防止多次编译)
-
#ifdef
ifdef就是if define示例: #ifdef x //如果宏定义了x,则执行语句块1 语句块1 #endif - - - - - - - - - - // 其他形式如下: #ifndef x #define x 语句块1 #else 语句块2 #endif - - - - - - - - - - #if 表达式 语句块1 //表达式为真 #else 语句块2 //表达式为假 #endif
-
#define
“define” 为 宏定义命令。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏替换”。宏定义是由源程序中的宏定义命令完成的,宏替换是由预处理程序自动完成。
优点:
1. 方便程序的修改
2. 提高程序的运行效率。使用带参数的宏定义可完成函数调用的功能,能减少系统开销,提高运行效率
3. 宏定义可完成简单操作,但复杂操作还是要用函数调用,而且宏定义所占用的目标代码空间相对较大
缺点:
4. 避开类型检查,增加出bug几率
- 其他 #ifndef, #define, #endif 讲解: