auto int a = 3;
auto b = 1;
auto和其他变量类型有明显的区别:
有点像auto的反函数,decltype可以从一个变量或者表达式中得到其类型。
int i;
decltype(i) j = 0;
vector<int> tmp;
decltype(tmp.begin()) k;
for(k = tmp.begin;k!=tmp.end();++k){}
enum{OK, ERROR} flag; //匿名类型枚举变量
decltype(flag) flag2;
返回类型后置:在函数名和参数列表后面指定返回类型
int func(int,double);
auto func2(int,double)->int; //结果会发生折断
auto func3(int a,double b)->decltype(a+b);
template<typename T1, typename T2>//根据需要获得返回值类型,更加智能
auto mul(const T1 &t1, const T2 &t2) -> decltype(t1*t2)
{
return t1*t2;
}
class B{
public:
int data{1};
int data2 = 1;
string name{"mike"};
};
struct Test{
int a;
int b;
char name[50];
};
struct Test tmp = {1,2,"mike"}; //列表初始化
int b = {1};
//int arr[] = {1,2,3}; 曾经
int arr[]{1,2,3};
//防止类型收窄
int a = 1024;
char b = {a}; //会报错,int 转换到char会类型收窄
vector<int> arr;
for(auto &tmp : arr);
for(auto tmp : arr);
assert(flag == true)
//static_assert只能是常量表达式,不能是变量
//static_assert(条件,“提示字符串”);
//(如检测程序平台64位不允许执行)
static_assert(sizeof(void *)==8,"64位系统不支持");
用于不抛出异常
void func(){throw 1;} //抛出异常
void func1() throw() {} //函数体内不允许抛出异常
void func2() noexcept {} //函数体内不允许抛出异常
以前枚举的局限性
- 不同枚举中相同枚举单元会被认为是重定义
- 本质当成是一个int类型
强枚举类型
- enum后面加上class或struct修饰
- 可以指定成员变量的类型
int main() {
enum stat1 {OK,ERROR};
enum stat2 {OK,ERROR}; // 错误 C2365“main::ERROR”: 重定义;
}
//强枚举类型
int main() {
enum class stat1 { OK, ERROR };
enum class stat2 { OK, ERROR };
enum class stat3: char{ OK,ERROR }; // 指定成员变量的类型
stat1 flag = stat1::OK; //支持,需要指定作用域
cout << (flag == stat1::OK)<<" "<< sizeof(stat3::OK); //1 1
}
常量表达式允许一些计算发生在编译时,即发生在代码编译而不是运行时。
这次优化可以避免每次程序允许时候都被计算,而只需编译时期计算一次即可。
限制
- 函数中只能有一个return语句(极少特例)
- 函数必须有返回值(不能是void函数)
- 在使用前必须已有定义
- return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式。
int getNum1() {return 3;}
constexpr int getNum2() {return 3;}
int main() {
//enum MyEnum{a1 = getNum1(), a2};//编译失败,枚举类型初始化必须是整型常量
enum MyEnum { a1 = getNum2(), a2 }; //ok,发生在编译期间
constexpr int tmp = 3;
enum MyEnum2 { a3 = tmp, a4 }; //ok,发生在编译期间
}
输出原始字符串
cout<<R"(hello,\n world)"<<endl;
C++11以前,一直没有继承控制的关键字,禁用一个类的进一步衍生比较麻烦。
C++11添加了两个继承控制关键字:final和override
final阻止类的进一步派生和虚函数的进一步重写
override确保派生类中声明的函数跟基类的虚函数有相同的签名
//final阻止类的进一步派生,虚函数的进一步重写
class A1 final{ //加上final,指定A1不能派生
int a;
};
class A2 {
public:
virtual void func() final{} // 派生类无法对该虚函数进行重写
};
class A3 {
public:
virtual void func(int a) {} //第一个虚函数不能使用override修饰
};
class B3:public A3{
public:
void func(int a) override {}
//重写虚函数的地方,加上override,要求重写的虚函数和基类一模一样的,否则编译错误
};
C++的类有四种特殊成员函数:默认构造函数、析构函数、拷贝构造函数、拷贝复制运算符。这些类的特殊成员函数负责创建、初始化、销毁或者拷贝类的对象。
如果一个程序员没有显式的为一个类定义某个特殊成员,而又需要用到该特殊成员函数的时,则编译器会隐式的为这个类生成一个默认的特殊成员函数。
但是,如果程序员为类显式的定义了非默认构造函数,编译器将不会再为他隐式的生成默认无参构造函数。
注意
- default只能修饰类中默认提供的四个成员函数;
class X {
public:
X(int i) {i = i;}
X() = default; //让编译器提供一个默认构造函数,效率比用户写的高
int i = 1;
};
int main() {
X x;
cout << x.i;
}
为了能够让程序员显式的禁用某个函数,C++11标准引入了一个新特性:“=delete”函数。程序员只需要在声明后“=delete”,就可以将该函数禁用。
class X {
public:
X() = default;
X(const X &b) = delete; //拷贝构造
X& operator==(const X&) = delete; //赋值重载
X(int i) = delete;
int i = 1;
};
int main() {
//X x(10); //無法調用
//X x1 = x; //拷贝构造 发生error
//x1 = x; //赋值重载 禁用
}
语法
template<class ... T> //T 模板参数包
void func(T... args){
cout<<sizeof...(args)<<endl;
} //args 函数参数包
func<int>(10);
func<int,char>(10,'a');
func<int, const char*>(10,"123");
参数包展开方式
递归方式展开
用递归函数展开参数包,需要提供一个参数包的展开函数和一个递归终止函数
//void debug() { //递归终止函数
// cout << "endl" << endl;
//
//}
template<class T> //递归终止函数 根据需要处理最后几个参数
void debug(T t) {
cout <<t<< " endl" << endl;
}
//可变参数的模板类型
template<class T1, class ... T2>
void debug(T1 first, T2... args) {
cout << first << " ";
debug(args...); //需要一个结束条件
}
int main() {
debug<int>(10);
debug<int, char>(10, 'a');
debug<int, const char*>(10, "123");
debug(1,2,3,4,'a','b','c');
}
非递归方式展开
template<class T> //递归终止函数 根据需要处理最后几个参数
void printf(T t) {
cout << t << " ";
}
//可变参数的模板类型
template< class ... T>
void expand(T... args) {
//借助逗号运算符
//通过初始化列表方式实现,最终a数组全为0
int a[] = { (printf(args),0)... };
cout << endl;
}
int main() {
expand<int>(10);
expand<int, char>(10, 'a');
expand<int, const char*>(10, "123");
expand(1,2,3,4);
}
继承方式展开参数包
可变参数模板类的展开一般需要定义2-3个类,包含类声明和特化的模板类
1、可变参数模板声明
2、递归继承模板类
3、边界条件
//1、可变参数模板声明
template<class ...Tail>class car{};
//2、递归继承模板类
template<class Head, class ...Tail>
class car<Head,Tail...>:public car<Tail...> {//递归继承本身
public:
car() { cout << "type = " << typeid(Head).name() << endl;}
};
//3、边界条件
template<>
class car<>{};
int main() {
car<int, char, char> c;
}
模板递归和特例化方式展开参数包
1、变长参数模板声明
2、变长模板类定义
3、边界条件
//1、变长模板声明
template<int ...Tail>class test{};
//2、变长模板类定义
template<int Head, int ...Tail>
class test<Head,Tail...>{
public:
static const int val = Head * test<Tail...>::val;
};
//3、边界条件
template<>
class test<>{
public:
static const int val = 1;
};
int main() {
cout<< test<1, 2, 3>::val;
}
概念:
一般来说,闭包就是带有上下文的函数。说白了就是有状态的函数。更直接点就是一个类。
一个函数带了状态,就是闭包了。带了状态就是说有属于自己的变量,这些变量的值是创建闭包的时候设置的,并在调用闭包的时候,可以访问这些变量。
函数是代码,状态是一组变量,将代码和一组变量捆绑(bind),就形成了闭包。
闭包的状态捆绑,必须发生在运行时
//仿函数
class Test {
public:
Test(int i ):r(i) {}
//仿函数,重载()
int operator()(int temp){
return temp + r;
}
private:
int r;
};
int main() {
Test t(10);
cout << t(1);
}
std::bind可以预先把可调用实体的某些参数绑定到已有变量,产生一个新的可调用实体,这种机制在回调函数中使用广泛。
#include
#include
using namespace std;
//普通函数
void test1() {
cout << __func__ <<"(" << ")" << endl;
}
//类中静态函数
class test2 {
public:
static int test_func(int a) {
cout << __func__ << "(" << a << ")" << endl;
return a;
}
};
//类中仿函数
class test3 {
public:
int operator()(int a) {
cout << __func__ << "(" << a << ")" << endl;
return a;
}
};
int main() {
//1.绑定普通函数
function<void(void)> f1 = test1;
f1(); //等价于test1()
bind(test1)();
bind(f1)();
cout << endl;
//2.绑定类中静态函数
function<int(int)> f2 = test2::test_func;
f2(2); //等价于调用test2::test_func(2)
bind(test2::test_func, 2)();
bind(f2, 2)();
cout << endl;
//3.绑定类中的仿函数
test3 obj;
function<int(int)> f3 = obj;
f3(3); //等价于调用obj(3)
bind(obj, 3)();
bind(f3, 3)();
bind(f3, placeholders::_1)(3); //_1占位符,运行时替换指定参数,且有顺序
using namespace placeholders;
bind(f3, _1)(3);
}
基本构成:
[ ] 表示一个lamdba开始,必须存在。参数只能使用那些lamdba可见范围内的局部变量,包括lamdba所在类的this,存在以下形式:
- 空:不捕获任何变量
- =:函数体内可以使用lamdba所在作用范围内所有可见局部变量,且是值传递方式(相当于编译器自动为给我们按值传递了所有局部变量)
- &:可见局部变量的引用传递
- this:函数体内可以使用lamdba所在类中的成员变量、全局变量(this可以使用的变量),不可以捕获局部变量;
- a:将a按值传递,且默认情况时const,要修改需要指定mutable;
- &a:按引用传递a
- a,&b:a按值传递,b按引用传递
- =,&a, &b :除了a,b引用传递,其余按值传递
- &,a, b:除了a,b按值传递,其余按引用传递。
int a=0, b=0; int c, d; auto f = [&,a, b](int x, int y) { //a,b不可修改,可以使用mutable修改 c = 1; d = 1; cout << c << " " << d << endl; cout << a << " " << b << endl; cout << x << " " << y << endl; }; f(3, 3);
与仿函数的区别
仿函数使用前需要实例化类,要调用构造函数,耗费时间。
lamdba不需要定义类,直接利用函数输出结果。
事实上,仿函数是编译器实现lamdba的一种方式,通常**编译器都是把lambda表达式转化为一个仿函数对象。**因此,在C++11中,lamdba可以视为仿函数的一种等价形式。
lambda返回类型
lamdba表达式类型在C++11中被称为闭包类型,每一个lambda表达式则会产生一个临时对象(右值),因此,严格的说,lambda函数并非函数指针。
不过C++标准却允许lambda表达式向函数指针转换,前提是lambda函数没有捕获任何变量([ ]),且函数指针所致的函数原型,必须和lambda函数有这相同的调用方式。