捕获异常
class A
{
public:
A()
{
}
~A()
{
std::cout << "I am done" << std::endl;
}
};
void test()
{
try
{
A a;
throw std::runtime_error("sdss");//捕获异常能保证A 的析构函数正确执行
}
catch(std::exception e)
{
std::cout << e.what() << std::endl;
}
}
捕获所有异常
void test2()
{
try{
throw std::runtime_error("sdss");
}catch(...)
{
}
}
noexcept
函数所有声明和定义语句均需要包含noexcept
c++11 之前不抛出异常的声明方式是throw()
void mm()noexcept;
void mm()noexcept
{
}
void nn()throw();
void nn()throw()
{
}
noexcept(bool)
noexcept接受一个可选参数bool
void re()noexcept(true)
{
}
noexcept(re())返回个bool 变量,其中re()抛出异常则返回false, 否则返回true
void mm2()noexcept(noexcept(re()))//mm2和re抛出异常表现一致
{
}
函数指针及该函数所指的函数必须具有一致的异常说明
但是如果显示声明了指针可能抛出异常的话,则该指针能够指向任何抛出异常和不抛出异常的函数
void re()noexcept(true)
{
}
void m2()
{
throw std::runtime_error("sss");
}
int main()
{
void (*pf1)() noexcept = re;//pf1 显示声明为不抛出异常, 和re函数的异常说明一致
void (*pf2)() noexcept = m2; //错误,m2没有声明不抛出异常,但是pf2被声明了抛出异常. But 编译器能通过,编译器不会检测异常声明
void (*pf3)() = re;//我们显示声明了pf3可能抛出异常,则该指针能指向任何函数,也就是既可以指向抛出异常的函数,也可以指向不抛出异常的函数
pf2();//能够正确运行
}
exception 继承体系
最顶层:exception
继承于exception的类: std::bad_cast, std::logic_error, std::runtime_error, std::bad_alloc
继承于std::runtime_error的类 : std::overflow_error, std::underflow_error, std::range_error
继承于std::logic_error的类: std::domain_error, std::invalid_argument, std::out_of_range, std::length_error
可以写自己的错误类型
class M:public std::runtime_error
{
public:
explicit M(const std::string &s):std::runtime_error(s)
{
}
};
void test()
{
try
{
throw M("haha");
}
catch( M e)
{
std::cout << e.what() << std::endl;
}
}
命令空间
namespace nn
{
}
内联命名空间
//nn.h
inline namespace nn//第一次出现的时候必须标注为inline
{
}
namespace nn//再写的时候可以省略inline
{
}
//main.cpp
namespace LL
{
#include"nn.h"
int n = 0;
void test()
{
m = 10;//这里的m即是nn.h中 的m。并且LL中不能再有同名变量m
std::cout << m << std::endl;
}
}
未命名空间
未命名空间中定义的变量具有静态生命周期,第一次使用前创建,并且到程序结束时销毁
只能在给定文件内不连续,但是不能跨文件。
未命名空间的变量名与全局作用域的变量名要有所区别
int i;
namespace
{
int i;
void set()
{
i = 10;
}
void test()
{
std::cout << i << std::endl;
}
}
int main()
{
//std::cout << i << std::endl;//把这句话注释掉,程序正确通过编译,打开则通不过。因为未命名空间内的变量是在第一次使用前创建,所以注释掉的时候,相当于该名称还没有使用,而打开的时候,相当于使用了该命名空间的变量,因此编译器检测到二义性
}
using声明和using 指示
namespace LongName
{
int l;
int p = 10;
}
void usingtest()//using 声明
{
namespace ln = LongName;//命名空间的别名
ln::l = 10;
std::cout << ln::l << std::endl;
using ln::p;//一条using语句一次只能引入命名空间的一个成员。作用域从using声明,到using 声明所在的作用域结束为止
std::cout << p << std::endl;
}
int l = 5;//全局变量
void usingtest2()//using指示
{
using namespace LongName;//using 指示,和using 声明类似,不过using指示无法指明哪些名字是不可见的,因为空间名字都是可见的
//l=10;//错误,二义性
::l = 10;//正确,指向全局变量l
LongName::l = 5;//正确
}
using 指示可以用在命名空间本身的实现文件中
//A.h
namespace A
{
}
//A.cpp
using namespace A;
实参查找和类类型的形参
std::string s;
std::cin >> s;//等价于 operator>>(std::cin, s);
//operator>> 是定义在标准库string 中, string 又在std命名空间中
//正常情况下应该写成 using std;然后才可以直接写 operator>>(std::cin,s)
//但是c++ 命名空间中名字的隐藏规则有个例外。
//当我们给函数传递一个类类型的参数时,除了常规的作用域查找外,还会查找实参类所属的命名空间。
//因此编译器发现operator>>时,首先 在当前作用域查找合适的函数,接着查找语句外层的作用域,随后由于>>表达式的形参是类类型的,所以编译器 会查找cin和s所属的命名空间。编译器会查找istream和string的命名空间std.在std查找时,编译器找到了string的输出运算符函数
namespace A//如果这里namespace是个匿名空间的话,则其作用域将提升至整个文件,这个时候匿名空间的print会与下面的相同形参的print 函数会冲突
{
void print(int k)
{
std::cout << "int" << std::endl;
}
void print(double t)
{
std::cout << "double" << std::endl;
}
}
void print(int k)
{
std::cout << "all" << std::endl;
}
int main()
{
using A::print;//将会引用A空间里的print 的所有重载版本, 并且会隐藏全局函数print
print(1);// 输出"int"
}
多重继承
class A
{
public:
A()
{
}
A(const std::string &)
{
}
};
class B
{
public:
B()
{
}
B(const std::string &)
{
}
};
class C: public A, public B
{
public:
using A::A;
using B::B;
C()
{
}
};
int main()
{
C c;//错误,C 需要实现自己的C(const std::string&)版本
}
多重继承下,不同基类实现同一个函数,将会引发函数调用错误
class A
{
public:
A()
{
std::cout << "A" << std::endl;
}
A(const std::string &)
{
}
};
class B
{
public:
B()
{
std::cout << "B" << std::endl;
}
B(const std::string &)
{
}
};
void print(A &a)
{
}
void print(B &b)
{
}
class C: public A, public B
{
public:
using A::A;
using B::B;
C(const std::string &s):A(s),B(s)
{
}
};
int main()
{
C c("haha");
print(c);//错误,二义性
print(static_cast<A&>(c));//正确
}
虚继承
考虑这样一个例子
class M
{
public:
M()
{
std::cout << "M" << std::endl;
m = new char;
}
~M()
{
std::cout << "end M" << std::endl;
}
private:
char *m;
};
class A : public M
{
public:
A()
{
std::cout << "A" << std::endl;
}
~A()
{
std::cout << "End A" << std::endl;
}
};
class B: public M
{
public:
B()
{
std::cout << "B" << std::endl;
}
~B()
{
std::cout << "end B" << std::endl;
}
};
class C:public A,public B
{
public:
C()
{
std::cout << "C" << std::endl;
}
~C()
{
std::cout << "end C" << std::endl;
}
};
int main()
{
C c;
}
A,B继承M, C 同时继承 A 和B, 上述程序的执行结果中将会输出两遍M的构造和析构函数体
默认情况下,C 将含有M的2个子对象。这种情况和我们预想的可能不一样,假设我们预想M应当只有一个对象。这个时候,就需要用到虚继承了。
class M
{
public:
M()
{
std::cout << "M" << std::endl;
}
~M()
{
std::cout << "end M" << std::endl;
}
};
class A : public virtual M //public 后面添加virtual即可
{
public:
A()
{
std::cout << "A" << std::endl;
}
~A()
{
std::cout << "End A" << std::endl;
}
};
class B: public virtual M //public 后面添加virtual即可
{
public:
B()
{
std::cout << "B" << std::endl;
}
~B()
{
std::cout << "end B" << std::endl;
}
};
class C:public A,public B
{
public:
C()
{
std::cout << "C" << std::endl;
}
~C()
{
std::cout << "end C" << std::endl;
}
};
int main()
{
C c;
}
这里共享的M子对象,就被称为虚基类。无论虚基类在继承体系中出现多少次,派生类都只会包含唯一一个共享的虚基类子对象。
程序的输出结果是
M
A
B
C
end C
end B
end A
end M
可以看出C的构造函数顺序是,先构造虚基类,再依次向上构造继承类。析构的时候,则按照相反顺序。
可以再看看,如果把两个virtual删除,也就是不让M成为虚基类的输出结果
M
A
M
B
C
end C
end B
end M
end A
end M
构造顺序按照继承顺序依次构造每个类,遇到继承的类有基类的时候,则先构造其基类对象,再构造派生部分。析构函数则逆序。
如果继承多个虚基类的话,则按照继承的顺序依次构造相应虚基类