关键字及新语法
1.auto关键字及用法
-
auto关键字能做什么?
auto并没有让C++成为弱类型语言,也没有弱化变量什么,只是使用auto的时候,编译器根据上下文情况,确定auto变量
-
auto不能做什么?
auto作为函数返回值时,只能用于定义函数,不能用于声明函数。
如果在头文件中定义使用,编译无法通过。
-
auto关键字使用时必须初始化
2. nullptr关键字及用法
nullptr | NULL |
---|---|
一个指针类型(nullptr_t) | 本身是一个int类型 |
c++下可以隐式的强制转化成任意的指针类型 | NULL不可以 |
3. for循环语法
示例:
int main()
{
int numbers[] = { 1,2,3,4,5 };
std::cout << "numbers:" << std::endl;
for (auto number : numbers)
{
std::cout << number << std::endl;
}
}
智能指针 shared_ptr unique_ptr weak_ptr
1. shared_ptr
std::shared_ptr 是一种智能指针,它能够记录多少个shared_ptr共同指向一个对象,从而消除显示的调用delete,当引用计数变为零的时候就会将对象自动删除。
但还不够,因为使用 std::shared_ptr仍然需要使用new来调用,这使得代码出现了某种程度上的不对称。
std::make_shared 就能够用来消除显示的使用new,所以std::make_shared会分配创建传入参数中的对象,并返回这个对象类型的std::shared_ptr指针
#include <iostream>
#include <memory>
void foo(std::shared_ptr<int> i)
{
(*i)++;
}
int main()
{
// auto pointer = new int(10); // 非法, 不允许直接赋值
// 构造了一个 std::shared_ptr
auto pointer = std::make_shared<int>(10);
foo(pointer);
std::cout << *pointer << std::endl; // 11
// 离开作用域前,shared_ptr 会被析构,从而释放内存
return 0;
}
std::shared_ptr 可以通过get()方法来获取原始指针,通过reset()来减少一个引用计数,并通过get_count()来查看一个对象的引用计数
auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已 reset
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl;//0,pointer3已 reset
2. unique_ptr
std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证了代码的安全
std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法
make_unique 并不复杂,C++11 没有提供 std::make_unique,c++ 14中已有,可以自行实现:
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args )
{
return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}
#include <iostream>
#include <memory>
struct Foo
{
Foo() { std::cout << "Foo::Foo" << std::endl; }
~Foo() { std::cout << "Foo::~Foo" << std::endl; }
void foo() { std::cout << "Foo::foo" << std::endl; }
};
void f(const Foo &)
{
std::cout << "f(const Foo&)" << std::endl;
}
int main()
{
std::unique_ptr<Foo> p1(std::make_unique<Foo>());
// p1 不空, 输出
if (p1)
p1->foo();
std::unique_ptr<Foo> p2(std::move(p1));
f(*p2); // p2 不空, 输出
// p2 不空, 输出
if(p2)
p2->foo();
// p1 为空, 无输出
if(p1)
p1->foo();
p1 = std::move(p2);
// p2 为空, 无输出
if(p2)
p2->foo();
std::cout << "p2 被销毁" << std::endl;
// p1 不空, 输出
if (p1)
p1->foo();
// Foo 的实例会在离开作用域时被销毁
}
3. weak_ptr
std::weak_ptr是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用,std::weak_ptr没有*运算符和 -> 运算符,所以不能够对资源进行操作,它的唯一作用就是用于检查 std::shared_ptr是否存在,expired() 方法在资源未被释放时,会返回 true,否则返回 false。
#include <iostream>
#include <memory>
class A;
class B;
class A
{
public:
// A 或 B 中至少有一个使用 weak_ptr
std::weak_ptr<B> pointer;
~A()
{
std::cout << "A 被销毁" << std::endl;
}
};
class B
{
public:
std::shared_ptr<A> pointer;
~B()
{
std::cout << "B 被销毁" << std::endl;
}
};
int main()
{
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
return 0;
}
STL容器
std::forward_list单项列表容器
从++新增的线性表,与list区别在于它是单向链表。我们在学习数据结构的时候都知道,链表在对数据进行插入和删除是比顺序存储的线性表有优势,因此在插入和删除操作频繁的应用场景中,使用list和forward_list比使用array、vector和deque效率要高很多。
示例:
#include <forward_list>
int main()
{
std::forward_list<int> numbers = {1,2,3,4,5,4,4};
std::cout << "numbers:" << std::endl;
for (auto number : numbers)
{
std::cout << number << std::endl;
}
numbers.remove(4);
std::cout << "numbers after remove:" << std::endl;
for (auto number : numbers)
{
std::cout << number << std::endl;
}
return 0;
}
多线程
1. std::thread
std::thread为C++ 11的线程类,使用方法和boost接口一样,非常方便,同时,C++ 11的std::thread解决了boost::thread中构成参数限制的问题,我想着都是得益于C++11的可变参数的设计风格。
示例代码:
#include <thread>
void threadfun1()
{
std::cout << "threadfun1 - 1\r\n" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "threadfun1 - 2" << std::endl;
}
void threadfun2(int iParam, std::string sParam)
{
std::cout << "threadfun2 - 1" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "threadfun2 - 2" << std::endl;
}
int main()
{
std::thread t1(threadfun1);
std::thread t2(threadfun2, 10, "abc");
t1.join();
std::cout << "join" << std::endl;
t2.detach();
std::cout << "detach" << std::endl;
}
join()会等待t1线程退出后才继续往下执行
detach()并不会
2.std::atomic
std::atomic为C++11分装的原子数据类型。
什么是原子数据类型?
从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。
我们下面通过一个测试例子说明原子类型std::atomic_int的特点。
lamda表达式
示例
int main()
{
auto add= [](int a, int b)->int{
return a + b;
};
int ret = add(1,2);
std::cout << "ret:" << ret << std::endl;
return 0;
}
解释:
第3至5行为lamda表达式的定义部分
[]:中括号用于控制main函数与内,lamda表达式之前的变量在lamda表达式中的访问形式;
(int a,int b):为函数的形参
->int:lamda表达式函数的返回值定义
{}:大括号内为lamda表达式的函数体。
func 用于获得当前函数名字符串的宏
const char * Stanard_Macros(void)
{
//......
return __func__; //返回值:Stanard_Macros
}
_Pragma 预处理操作符,与#pragma功能相同,不过因为支持传递字符串,所以可以用宏命令替代,如对于经常使用的头文件单次包含。
//头文件防止重复包含
#pragma once;
_Pragma("once");
#define PRAGMA(x) _Pragma(#x) //宏命令中使用#可以实现替换字符串化
PRAGMA(once);
_Pragma与#ifndef #endif的比较
比较 | _Pragma | #ifndef #endif |
---|---|---|
相同点 | 都是防止头文件重复包含 | |
区别 | 微软编译器独有的,不支持跨平台 | 支持跨平台 |
区别 | 如果发现头文件被包含就不会打开头文件 | 每次都要打开头文件去判断头文件宏 |
缺陷 | 用在两个内容相同,文件名不同的头文件中时,这2个头文件都会包含进来,编译时就会出现重定义的错误 | 当2个头文件里的此处的宏名相同时,就会出现只包含一个头文件的问题,当使用未包含的头文件的函数或变量时就会报错找不到函数或变量 |
新增断言static_assert
断言帮助开发者快速定位问题违反程序前提条件的错误,不过断言只在程序运行时执行,这在某些情况下,是不可接受的,特别是对于模板实例化时出现的错误,应该在编译器就确定。在C++11中引入了static_assert断言来解决问题,它支持两个参数输入,一个是返回bool型的表达式,另一个是警告信息。
final和override控制
// final用来限制基类虚函数的对应的派生类不能重写该虚函数,从而避免了某些接口被重写覆盖;
// override则指定了函数必须重载基类的虚函数,否则编译通不过,这就避免了某些输入名或者原型不匹配等错误的发生。
class MathObject{
public:
virtual double Arith() = 0;
virtual void Print() = 0;
};
class Printable:public MathObject{
public:
double Arith() = 0; //纯虚函数仅允许为0
void Print() final{
std::cout<<"Output is: "<< Arith() <<std::endl;
}
};
class Add2:public Printable{
public:
Add2(double a, double b):x(a), y(b){}
double Arith() override{ //override指定函数为重载函数,进行检查(对于一定重载函数检查,避免了输入错误
return x+y;
}
// void Print(){} //编译会报错,因为父类声明了final,子类不允许重载
private:
double x, y;
}