详解C++之C++11的牛逼特性

一、列表初始化

1.1 C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。

int array1[] = {1,2,3,4,5};
int array2[] = {0};

对对于一些自定义类型,却不行.

vector<int> v{1,2,3,4,5};

在C++98中这样无法通过编译,因此需要定义vector之后,在使用循环进行初始赋值。
C++11扩大了用初始化列表的使用范围,让其适用于所有的内置类型和自定义类型,而且使用时,=可以不写

// 内置类型
int x1 = {10};
int x2{10}
// 数组
int arr1[5] {1,2,3,4,5}
int arr2[]{1,2,3,4,5};
// 标准容器
vector<int> v{1,2,3}
map<int,int> m{{1,1},{2,2}}
// 自定义类型
class Point
{
int x;
int y;
}
Power p{1,2};

1.2 多个对象的列表初始化
给类(模板类)添加一个带有initializer_list类型参数的构造函数即可支持多个对象的,列表初始化.

#include<initializer_list>
template<class T>
class Vector{
public:
	Vecto(initializer_list<T> l)
		:_capacity(l.size())
		,_size(0){
			_array = new T[_capacity];
			for(auto e : l)
				_array[_size++] = 3;
		}
private;
	T* _array;
	size_t _capacity;
	size_t _size;
};

二、变量类型推导

2.1 auto
在C++中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,简化程序的书写

// 不使用auto需要写很长的迭代器的类型
map<string,string> m;
map<string,string>::iterator it1 = m.begin();
// 使用auto就很简单
auto it2 = m.begin();

2.1 decltype 类型推导
auto使用时,必须对auto声明的类型进行初始化,否则编译器不能推导出auto的实际类型。
但是有些场景可能需要根据表达式运行后的结果进行类型推导。因为编译时,代码不会运行,auto也就…

template<class T1,class T2>
T1 Add(const T1& a,const T2& b){
	return a + b;
}

如果用加完后的结果作为函数的返回类型,可能会出错,这就需要程序运行完后才能知道结果的实际类型,即RTTI(运行时类型识别)
decltype可以根据表达式的实际类型推演出定义变量时所用的类型

// 推演表达式作为变量的定义类型
int a = 1,b=2;
decltype(a+b) c;
cout<<typeid(c).name()<<endl;

// 推演函数的返回值类型
void GetMemory(size_t size){
	return malloc(size);
}
cout<<typeid(decltype(GetMemory)).name()<<endl;

三、基于范围for的循环

vector<int> v{1,2,3,4,5};
for(const auto& e : v)
	cout<<e<<' ';
cout<<endl;

四、final和override
在我的多态的文章中有介绍:https://www.jb51.net/article/162078.htm

五、委派构造函数
委派构造函数可以通过委派其它构造函数,使多构造函数的类编写更加容易

class Info
{
public;
	Info()
		:_type(0)
		,_name('s')
		{}
	Info(int type)
		:_type(type)
		,_name('a')
		{}
	Info(char a)
		:_type(0)
		,_name(a)
		{}
pirvate;
	int _type;
	char _name;
};

上面的构造函数除了初始化列表不同之外,其它部分都是类似的,代码重复,可以使用委派构造函数
委派构造函数就是把构造的任务委派给目标构造函数来完成类的构造

class Info
{
// 目标构造函数
public:
	Info()
		:_type(0)
		,_a('a')
	{}
// 委派构造函数
	Info(int type)
		:Info()
		{
			_type = type;
		}
private;
	int _type = 0;
	char _a = 'a';
};

在初始化列表中调用“基准版本”的构造函数称为委派构造函数,而被调用的“基准版本”则称为目标构造函数

六、默认函数控制
在C++中对于空类,编译器会生成一些默认的成员函数,如果在类中显式定义了,编译器就不会重新生成默认版本。但是如果在一个类中声明了带参的构造函数,如果又需要定义不带参的实例化无参的对象。这时候编译器是有时生成,有时不生成,就会造成混乱,C++11可以让程序员自己控制是否需要编译器生成。

6.1 显式缺省函数
在C++11中,可以在默认函数定义或声明时加上=default,来让编译器生成该函数的默认版本。

class A
{
public:
	A(int a)
		:_a(a)
	{}
	A() = default; // 显式缺省构造函数
	A& operator=(const A& a); // 在类中声明,在类外定义时,让编译器生成默认赋值运算符重载
private:
	int _a;
};
A& A::operator=(const A& a) = default;

6.2 删除默认函数
要想限制一些默认函数的生成,在C++98中,可以把该函数设为私有,不定义,这样,如果有人调用就会报错。在C++11中,可以给该函数声明加上=delete就可以。

class A
{
A(int a)
	:_a(a)
{}
A(constA&) = delete; // 禁止编译器生成默认的拷贝构造函数
private:
	int _a;
};

七、右值引用
7.1 移动语义

class String
{
public:
	String(char* str = '")
	{
		if(str == nullptr)
			_str = "";
		_str = new char[strlen(str)+1];
		strcpy(_str,str);
	}
	String(const String& s)
		:_str(new char[strlen(c._str)+1])
	{
		strcpy(_str,s._str);
	}
	~String()
	{
		if(_str)
			delete[] _str;
	}
private:
	char* _str;
};

String GetString(char* pStr)
{
	String strTemp(pStr);
	return strTemp;
}

int main()
{
	String s1("hello");
	String s2(GetString("world"));
	return 0;
}

在上面的代码中,GetString函数返回的临时对象,将s2拷贝成功之后,立马销毁了(临时对象
的空间被释放);而s2拷贝构造的时,又需要分配空间,一个刚释放,一个又申请,有点多此一举,那能否把GetString返回的临时对象的空间直接交给s2呢?这样s2也不需要重新开辟空间了。

移动语义:将一个对象资源移动到另一个对象中的方式,在C++中要实现移动语义,必须使用右值引用.

7.2 C++中的右值
右值引用,顾名思义就是对右值的引用。在C++中右值由纯右值和将亡值构成。

  • 纯右值:用于识别变量和一些不跟对象关联的值。比如:常量、运算符表达式等、
  • 将亡值:声明周期将要结束的对象。比如:在值返回时的临时对象

7.3 右值引用
格式:类型&& 应用变量名字 = 实体;
使用场景:
1、与移动语义相结合,减少必须要的资源的开辟,提高运行效率

String&& GetString(char* pStr)
{
	String strTemp(pStr);
	return strTemp;
}

int main()
{
	String s1("hello");
	String s2(GetString("world"));
	return 0;
}

2、给一个匿名对象取别名,延长匿名对象的生命周期

String GetString(char* pStr) {
return String(pStr);
}
int main()
{
String&& s = GetString("hello");
return 0; }

注意

  • 右值引用在定义时必须初始化
  • 右值引用不能引用左值
int a = 10;
int&& a1; // 未初始化,编译失败
int&& a2 = a; // 编译失败,a是一个左值
// 左值是可以改变的值

7.4 std::move()
C++11中,std::move()函数位于头文件中,它可以把一个左值强制转化为右值引用,通过右值引用使用该值,实现移动语义。该转化不会对左值产生影响.
注意:其更多用在生命周期即将结束的对象上。

7.5 移动语义中要注意的问题
1、在C++11中,无参构造函数/拷贝构造函数/移动构造函数实际上有三个版本

Object()
Object(const T&)
Object(T&&)

2、如果将移动构造函数声明为常右值引用或者返回右值的函数声明为常量,都会导致移动语义无法实现

String(const String&&);
const String GetString();

3、C++11默认成员函数,默认情况下,编译器会隐士生成一个移动构造函数,是按照位拷贝来进行。因此在涉及到资源管理时,最好自己定义移动构造函数。

class String
{
public:
	String(char* str = "")
	{
		if(str == nullptr)
			str = "";
		_str = new char[strlen(str)+1];
		strcpy(_str,str);
	}
	// 拷贝构造
	// String s(左值对象)
	String(const String& s)
		:_str(new char[strlen(s._str) + 1])
		{
			strcpy(_str,s_str);
		}
	// 移动构造
	// String s(将亡值对象)
	String(String&& s)
		:_str(nullptr)
		{
			swap(_str,s._str);
		}
	// 赋值
	String& operator=(const String& s)
	{
		if(this != &s)
		{
			char* tmp = new char[strlen(s._str)+1];
			stcpy(tmp,s._str);
			delete[] _str;
			_str = tmp;
		}
		return *this;
	}
	// 移动赋值
	String& operator=(String&& s)
	{
		swap(_str,s._str);
		return *this;
	}
	~String()
	{
		if(_str)
			delete[] _str;
	}
	// s1 += s2 体现左值引用,传参和传值的位置减少拷贝
	String& operator+=(const String& s)
	{
		// this->Append(s.c_str());
		return *thisl
	}
	// s1 + s2
	String operator+(const String& s)
	{
		String tmp(*this);
		// tmp.Append(s.c_str());
		return tmp;
	}
	const char* c_str()
	{
		return _str;
	}
private:
	char* _str;
};
int main()
{
	String s1("hello"); // 实例化s1时会调用移动构造
	String s2("world");
	String ret
	ret = s1 + s2 // +返回的是临时对象,这里会调用移动构造和移动赋值,减少拷贝

	vector<String> v;
	String str("world");
	v.push_back(str); // 这里调用拷贝构造函数
	v.push_back(move(str)); // 这里调用移动构造,减少一次拷贝
	return 0;
}

总结:
左值:可以改变的值;
右值: 不可以改变的值(常量,表达式返回值,临时对象)
左值引用: int& aa = a; 在传参和传值的位置使用,减少拷贝,提高效率
右值引用: int&& bb = 10; 在传值返回和将亡值传参时,通过调用移动构造和移动赋值,减少拷贝,提高效率。
const 左值引用可以引用右值
右值引用可以应用move后的左值

7.6 完美转发
完美转发是指在函数模板中,完全按照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数

void Func(int x)
{
	// ......
}
template<typename T>
void PerfectForward(T t)
{
	Fun(t);
}

PerfectForward为完美转发的模板函数,Func为实际目标函数,但上面的转发还不够完美,完美转发是目标函数希望将参数按照传递给转发函数的实际类型转给目标函数,而不产生额外开销,就好像没有转发者一样.
所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,就转发左值;如果是右值,就转发右值。(这样是为了保留在其他函数针对转发而来的参数的左右值属性进行不同处理,比如参数为左值时实施拷贝语义、参数为右值时实施移动语义)
在C++11中,通过forward函数来实现完美转发。

void Fun(int &x){cout << "lvalue ref" << endl;}
void Fun(int &&x){cout << "rvalue ref" << endl;}
void Fun(const int &x){cout << "const lvalue ref" << endl;}
void Fun(const int &&x){cout << "const rvalue ref" << endl;}
template<typename T>
void PerfectForward(T &&t){Fun(std::forward<T>(t));}
int main()
{
PerfectForward(10); // rvalue ref
int a;
PerfectForward(a); // lvalue ref
PerfectForward(std::move(a)); // rvalue ref
const int b = 8;
PerfectForward(b); // const lvalue ref
PerfectForward(std::move(b)); // const rvalue ref
return 0; }

八、lambda表达式
在C++98中,如果想对一个数据集合中的元素进行排序,可以使用std::sort()方法,但其默认按照小于比较,如果想排降序,则要传入第三个参数,可以使用std::greater()的比较方法,

vector<int> v{1,4,3,2,7,6,5};
// 默认按照小于比较,结果是升序
sort(v.begin(),v.end());
// 传入第三个模板参数std::greater<T>(),按照大于比较,默认是降序
sort(v.begin(), v.end(),greater<int>());

但是该方法只支持内置类型,对于用于自定义的类型就无能为力了,这是就需要用于自定义排序时的规则。目前我们可以通过函数指针,仿函数,lambda来解决。

1、lambda 表达式语法

[捕捉列表](参数列表)mutable->返回值类型{函数体}
捕捉列表:该列表出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表可以捕捉上下文中的变量供lambda函数使用
参数列表:与普通函数的参数列表一致。则可以连同()一起省略
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符,参数列表不可以省略(即使参数列表为空)
->返回值类型。用于追踪返回值类型。没有返回值时可以省略。返回值类型明确的情况下,也可以省略
{函数体}:在该函数体,除了可以使用参数外,也可以使用捕捉到的所有变量

!!!在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。

int main()
{
	// 最简单的lambda表达式
	[]{};
	// 省略参数列表和返回值类型,返回值类型有编译器推演为int
	int a=3,b=4;
	[=]{return a+3;};
	// 省略返回值类型
	auto fun1 = [&](int c){b = a + c;};
	// 各部分完整的lambda函数
	auto fun2 = [=,&b](int c)->int(return += a + c;);
	// 复制捕捉x
	int x = 10;
	auto add_x = [x](int a)mutable{x *= 2; return a + x;};
	return 0;
}

2、捕获列表说明
捕获列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是引用

a [var]:表示值传递方式捕获变量var
b [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
c [&var]:表示引用传递变量var
d [&]:表示引用传递捕获所有父作用域中的变量(this)
e [this]:表示值传递方式捕获当前的this指针

!!!:

a 父作用域包含lambda函数的语句块
b 语法上捕获列表可由多个捕获项组成,并以逗号分隔
	比如:[=,&a,&b]:以引用传递的方式捕获变量a 和 b,值传递的方式捕获其它所有变量.
	[&,a,this];值传递的方式捕获变量a和this,引用方式捕获其它变量。
	捕捉列表不允许变量重复传递,否则会导致编译错误。比如:[=,a]以传值的方式捕获了所有变量,又重复捕捉a
c 块作用域以外的lambda函数捕捉列表必须为空
e 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错
f lambda表达式之间不能相互赋值,即使看起来类型相同.
void (*PF)();
int main()
{
	auto f1 = []{cout<<"hello world"<<endl;};
	auto f2 = []{cout<<"hello world"<<endl;};
	f1= f2; // 这里会编译失败,提示找不到operator=()
	auto f3(f2); // 允许使用一个lambda表达式拷贝一个新的福分
	PF = f2; // 可以将lambda表达式赋值给相同类型的指针
	return 0;
}

3、lambda表达式与函数指针、仿函数

typedef bool (*GTF) (int, int);
bool greater_func1(int l, int r)
{
	return l > r;
}

struct greater_func2
{
	bool operator()(int l, int r)
	{
		return l > r;
	}
};

int main()
{
	// 函数指针
	GTF f1 = greater_func1; // typedef 定义
 // bool (*f1) (int, int) = greater_func1; // 不typedef ,直接原生写法,可读性差
 cout<< f1(1,2)<<endl;
 // 仿函数
 greater_func2 f2;
 cout<< f2(1,2)<<endl;
 // lamba表达式
 auto f3 = [] (int l, int r) ->bool{return l > r;};
 cout<< f3(1,2)<<endl;

	int a[] = {1,2,4,5,3,7,6,9,8};
	sort(a,a+sizeof(a)/sizeof(a[0]),f1);
	sort(a,a+sizeof(a)/sizeof(a[0]),f2);
	sort(a,a+sizeof(a)/sizeof(a[0]),f3);
	// sort函数第三个模板参数能接受函数指针,仿函数、lambda表达式,是因为其第三个参数是一个模板custom (2)
	template <class RandomAccessIterator, class Compare>
 void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

	return 0;
}

函数指针,仿函数,lambda用法上是一样的,但函数指针类型定义很难理解,仿函数需要实现运算符的重载,必须先定义一个类,而且一个类只能实现一个()operator的重载。(ep:对商品的不同属性实现比较就需要实现不同的类),要先定义好才能使用。而lambda可以定义好直接使用.

struct Goods
{
	string _name;
	double _price;
	double _appraise;
};

int main()
{
	Goods gds[] = { { "苹果", 2.1, 10 }, { "相交", 3, 8 }, { "橙子", 2.2, 7 }, { "菠萝", 1.5, 10 } };

	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool
	{return g1._price > g2._price; });

	sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool
	{return g1._appraise > g2._appraise; });

	return 0;
}

上面的例子就体现了其现做现用的特性。

4、lambda表达式的底层

class Rate
{
public:
Rate(double rate)
 : _rate(rate)
 {}
double operator()(double money, int year)
 {
return money * _rate * year;
 }
private:
double _rate;
};
int main()
{
// 函数对象
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
// 仿函数
auto r2 = [=](double monty, int year)->double{return monty*rate*year; };
r2(10000, 2);
return 0; }

函数对象将rate作为其成员变量,在定义对象时候给出初始值即可,lambda表达式通过捕获列表直接捕获该变量.


通过上面的图可以看出,实际在底层编译器对于处理lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator();
并且编译器是通过lambda_+uuid来唯一辨识一个lambda表达式的
九、线程库
C++11中引入了线程库,使得在C++在并行编程时可以不需要依赖第三方库,而且在原子操作中引入了原子类的概念。
要使用标准库中的线程,必须包含头文件

#include<iostream>
#include<thread>

void fun()
{
	std::cout << "A new thread!" << std::endl;
}
int main()
{
	std::thread t(fun);
	t.join();
	std::cout << "Main thread!" << std::endl;
	system("pause");
	return 0;
}


9.1 线程的启动
C++线程库通过构造一个线程对象来启动一个线程,该线程对象中包含了线程运行时的上下文环境,如:线程函数、线程栈、线程其实状态、以及线程ID等,把所有操作全部封装在一起,在同一传递给_beginthreadex()创建线程函数来实现(_beginthreadex是windows中创建线程的底层c函数)
std::thread()创建一个新的线程可以接受任意的可调用对象类型,包括lambda表达式,函数,函数对象,函数指针

// 使用lambda表达式作为线程函数创建线程
int main()
{
	int n1 = 1;
	int n2 = 2;
	std::thread t([&](int addNum){n1 += addNum; n2 += addNum; }, 3);
	t.join();
	std::cout << n1 << " " << n2 << std:: endl;
	system("pause");
	return 0;
}

9.1 线程的结束
启动一个线程后,当线程执行完毕时,如果护手线程使用的资源,thread库提供了两种选择。
1、join()
join():会主动等待线程终止,在调用进程中join(),当新的线程终止时,join()会清理相关的资源,然后返回,调用线程在继续向下执行。由于join()清理了线程的相关资源,thread对象与已销毁的线程就没有关系了,因此一个线程对象每次只能join()一次,如果多次调用join(),joinable()会返回false;

int main()
{
	int n1 = 1;
	int n2 = 2;
	std::thread t([&](int addNum){n1 += addNum; n2 += addNum; }, 3);
	std::cout << "join before,joinable=" << t.joinable() << std::endl;
	t.join();
	std::cout << "join after,joinable=" << t.joinable() << std::endl;
	system("pause");
	return 0;
}
// 执行结果:
join before,joinable=1
join after,joinable=0

2、detach()
detach:会从调用线程中分离出新的线程,之后不能再与新线程交互。这是调用joinable()会返回false。分离的线程会在后台运行,其所有权和控制权会交给C++运行库。C++运行库会保证在线程退出时,其相关资源能正确回收。

int main()
{
	int n1 = 1;
	int n2 = 2;
	std::thread t([&](int addNum){n1 += addNum; n2 += addNum; }, 3);
	std::cout << "join before,joinable=" << t.joinable() << std::endl;
	t.detach();
	std::cout << "join after,joinable=" << t.joinable() << std::endl;
	system("pause");
	return 0;
}

注意,必须在thread对象销毁之前作出选择,因为线程在join()或detach()之前,就可能已经结束,如果之后在分离,线程可能会在thread对象销毁之后继续运行。

9.3 原子性操作库
多线程最主要的问题是共享数据带来的问题(线程安全)。如果数据都是只读的,没有问题,因为只读不会影响数据,不会涉及数据的修改,所有线程都会获得同样的数据。但是,当多个线程要修改数据时,就会产生很多潜在的麻烦。

int sum = 0;

void fun(size_t num)
{
	for (size_t i = 0; i < num; i++)
		sum++;
}

int main()
{
	std::cout << "before,sum=" << sum << std::endl;
	std::thread t1(fun, 100000000);
	std::thread t2(fun, 100000000);
	t1.join();
	t2.join();
	std::cout << "After,sum=" << sum << std::endl;
	system("pause");
	return 0;
}

当fun的参数比较大时,就会产生和预期不相符的结果.
在C++98中可以通过加锁来保护共享数据。

int sum = 0;
std::mutex m;

void fun(size_t num)
{
	for (size_t i = 0; i < num; i++)
	{
		m.lock();
		sum++;
		m.unlock();
	}
}

虽然加锁结果了这个问题:但是它有一个缺陷:只要有一个线程在对sum++的时候,其它线程就会阻塞,会影响程序运行的效率,而且锁如果控制不好,或导致思索的问题。
因此在C++11中引入了原子操作。对应于内置的数据类型,原子数据类型都有一份对应的类型。

要使用以上的原子操作,需要添加头文件

#include<thread>
#include<mutex>
#include<atomic>

std::atomic_int sum{ 0 };

void fun(size_t num)
{
	for (size_t i = 0; i < num; i++)
	{
		sum ++; // 原子的
	}
}

int main()
{
	std::cout << "before,sum=" << sum << std::endl;
	std::thread t1(fun, 10000000);
	std::thread t2(fun, 10000000);
	t1.join();
	t2.join();
	std::cout << "After,sum=" << sum << std::endl;
	system("pause");
	return 0;
}

到此这篇关于详解C++之C++11的牛逼特性的文章就介绍到这了,更多相关C++11特性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++11的新特性简单汇总介绍 (一)

    什么是C++11 C++11是曾经被叫做C++0x,是对目前C++语言的扩展和修正,C++11不仅包含核心语言的新机能,而且扩展了C++的标准程序库(STL),并入了大部分的C++ Technical Report 1(TR1)程序库(数学的特殊函数除外). C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto.decltype,和模板的大量改进. 1. 概述 最近在看C++ Primer5 刚好看到一半,总结一下C++11里面确实加了很多新东西,如果没有任何了解,别说自己

  • 结合C++11新特性来学习C++中lambda表达式的用法

    在 C++ 11 中,lambda 表达式(通常称为 "lambda")是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象的简便方法. Lambda 通常用于封装传递给算法或异步方法的少量代码行. 本文定义了 lambda 是什么,将 lambda 与其他编程技术进行比较,描述其优点,并提供一个基本示例. Lambda 表达式的各部分 ISO C++ 标准展示了作为第三个参数传递给 std::sort() 函数的简单 lambda: #include <algorit

  • C++11新特性之智能指针(shared_ptr/unique_ptr/weak_ptr)

    shared_ptr基本用法 shared_ptr采用引用计数的方式管理所指向的对象.当有一个新的shared_ptr指向同一个对象时(复制shared_ptr等),引用计数加1.当shared_ptr离开作用域时,引用计数减1.当引用计数为0时,释放所管理的内存. 这样做的好处在于解放了程序员手动释放内存的压力.之前,为了处理程序中的异常情况,往往需要将指针手动封装到类中,通过析构函数来释放动态分配的内存:现在这一过程就可以交给shared_ptr去做了. 一般我们使用make_shared来

  • 结合C++11的新特性来解析C++中的枚举与联合

    枚举 枚举是用户定义的类型,其中包含一组称为枚举器的命名的整型常数. 语法 // unscoped enum: enum [identifier] [: type] {enum-list}; // scoped enum: enum [class|struct] [identifier] [: type] {enum-list}; // Forward declaration of enumerations (C++11): enum A : int; // non-scoped enum mu

  • C++11/14的新特性(更简洁)

    新的字符串表示方式--原生字符串(Raw String Literals) C/C++中提供了字符串,字符串的转义序列,给输出带来了很多不变,如果需要原生义的时候,需要反转义,比较麻烦. C++提供了,原生字符串,即字符串中无转义,亦无需再反义.详细规则见带码: #include <iostream> using namespace std; string path = "C:\Program Files (x86)\alipay\aliedit\5.1.0.3754";

  • 详解C++之C++11的牛逼特性

    一.列表初始化 1.1 C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定. int array1[] = {1,2,3,4,5}; int array2[] = {0}; 对对于一些自定义类型,却不行. vector<int> v{1,2,3,4,5}; 在C++98中这样无法通过编译,因此需要定义vector之后,在使用循环进行初始赋值. C++11扩大了用初始化列表的使用范围,让其适用于所有的内置类型和自定义类型,而且使用时,=可以不写 // 内置类型 int x1

  • 详解CentOS7下PostgreSQL 11的安装和配置教程

    1. 官网地址 官网上提供了安装参考步骤:点击此处查看官网 2. 安装RPM yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm 3. 安装客户端 yum install postgresql11 4. 安装服务器端 yum install postgresql11-server 5. 安装依赖包 yum install

  • 详解JavaScript中基于原型prototype的继承特性

    JavaScript 中的继承比较奇葩,无法实现接口继承,只能依靠原型继承. 原型链 原型就是一个对象,通过构造函数创建出来的实例会有指针指向原型得到原型的属性和方法.这样,实例对象就带有构造函数的属性方法和原型的属性方法,然后将需要继承的构造函数的原型指向这个实例,即可拥有这个实例的所有属性方法实现继承. 看下面演示代码: //声明超类,通过构造函数和原型添加有关属性和方法 function Super(){ this.property = true; } Super.prototype.ge

  • 详解Java5、Java6、Java7的新特性

    Java5: 1.泛型 Generics: 引用泛型之后,允许指定集合里元素的类型,免去了强制类型转换,并且能在编译时刻进行类型检查的好处.Parameterized Type作为参数和返回值,Generic是vararg.annotation.enumeration.collection的基石. A.类型安全 抛弃List.Map,使用List.Map给它们添加元素或者使用Iterator遍历时,编译期就可以给你检查出类型错误 B.方法参数和返回值加上了Type 抛弃List.Map,使用Li

  • 详解C#面相对象编程中的继承特性

    继承(加上封装和多态性)是面向对象的编程的三个主要特性(也称为"支柱")之一. 继承用于创建可重用.扩展和修改在其他类中定义的行为的新类.其成员被继承的类称为"基类",继承这些成员的类称为"派生类".派生类只能有一个直接基类.但是,继承是可传递的.如果 ClassB 派生出 ClassC,ClassA 派生出 ClassB,则 ClassC 会继承 ClassB 和 ClassA 中声明的成员. 注意 结构不支持继承,但可以实现接口. 从概念上来

  • SpringBoot结合Redis实现序列化的方法详解

    目录 前言 配置类 配置 Jackson2JsonRedisSerializer 序列化策略 配置  RedisTemplate 配置缓存策略 测试代码 完整代码 前言 最近在学习Spring Boot结合Redis时看了一些网上的教程,发现这些教程要么比较老,要么不知道从哪抄得,运行起来有问题.这里分享一下我最新学到的写法 默认情况下,Spring 为我们提供了一个 RedisTemplate 来进行对 Redis 的操作,但是 RedisTemplate 默认配置的是使用Java本机序列化.

  • vue使用axios实现文件上传进度的实时更新详解

    axios 简介 axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征: 从浏览器中创建 XMLHttpRequest 从 node.js 发出 http 请求 支持 Promise API 拦截请求和响应 转换请求和响应数据 取消请求 自动转换JSON数据 客户端支持防止 CSRF/XSRF 引入方式: $ npm install axios //使用淘宝源 $ cnpm install axios //或者使用cdn: <script s

  • C++ 类的继承与派生实例详解

     C++ 类的继承与派生实例详解 继承性是面向对象程序设计最重要的特性之一,使软件有了可重用性,C++提供的类的继承机制. 继承与派生的概念 一个新类从已有的类那里获得已有的特性,这种现象称为类的继承.同样也可以说成已有的类派生出来了新的类.类A继承自类B也就是类B派生了类A.所以继承和派生的关系就像小学时把字句和被字句的造句一样.有了继承与派生后,就有了父类/基类与子类/派生类,C++中将类B称为父类/基类,将类A称为子类/派生类. 派生类的声明: #include <iostream> u

  • webpack5新特性Asset Modules资源模块详解

    目录 正文 图片打包(asset/resource) publicPath asset/inline 模块 asset 模块 asset/source 模块 正文 webpack 可以将很多类型的文件写入最后打包的js文件,写入的方法有两种,一个是 Asset Modules 另一个是 Loaders 这一篇我们就来讨论 Asset Modules.Asset Modules(资源模块)是webpack5的新特性,它允许使用资源文件(字体,图标等)而无需配置额外 loader, webpack低

  • PHP 5.6.11 访问SQL Server2008R2的几种情况详解

    PHP天生支持MySQL,但是有时候也想让它访问SQL Server,该怎么办呢? 最近找了点资料,测试成功了PHP访问SQLSvr的几种情况,限于时间,还没有测试更多不同环境,把测试过的记录如下: 测试环境:win7 x64 sp1,IIS 7.5, Apache 2.4 32位版本,PHP 5.2.6 win32, PHP 5.6.11 win32 ts(线程安全版) 注:由于"Microsoft Drivers for PHP for SQL Server"驱动程序目前只有32位

随机推荐