系列文章目录
一、类型
1.auto用法
注意要点:编译器在编译阶段完成对auto的推导,就必须能让编译器推出其类型。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
using namespace std;
double foo()
{
return 1.1;
}
struct Test
{
int a;
};
void func(vector<int> &tmp)
{
for (auto i = tmp.begin(); i != tmp.end(); ++i)
{
//……
}
}
int main(void)
{
auto b = 1; //b的类型就是int
auto c = foo(); //c的类型就是double
Test str = { 0 };
auto d = str; //d的类型就是struct Test
return 0;
}
________________________________________________________________________________________________________
auto易错点
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <vector>
using namespace std;
//2、vs2013不支持,函数形参是auto变量, qt确实可以
void func(auto a)
{
}
//3、auto变量不能作为自定义类型的成员变量
struct Test
{
int a;
auto b = 10;
};
int main(void)
{
//1、定义变量时,必须初始化
auto a;
a = 10;
auto b[3] = { 1, 2, 3 }; //4、不能是auto数组
//5、模板实例化类型不能是auto类型
vector<int> a;
vector<auto> b = { 1 };
system("pause");
return 0;
}
2.数据类型
定义数据类型
typedef int int32;
using my_int=int; //C++11方式
cout << is_same<int32, m_int>::value << endl;
decltype自动推导类型
float a;
double b;
decltype(a + b) c;
cout << typeid(c).name() << endl;
auto作为返回值和追踪返回类型
auto func2(int a, int b)
{
return a + b;
}
auto func3(int a, double b) -> decltype(a+b)
{
return a + b;
}
类中成员变量初始化
class A
{
public:
A(int i) : a(i) //参数列表初始化
{
//a = i;
}
int a;
};
class B
{
public:
int data{ 1 };
int data2 = 1;
A tmp{ 10 };//新的类初始化方法
string name{ "mike" };//新的类初始化方法
};
初始化列表
int a = 1;
char b = { 55}; //ok, 列表初始化
int c{ 2 }; //ok
cout<<b<<endl;
int arr[] = { 1, 2, 3 };
int arr2[]{ 1, 2, 3 };
{}类型转化可以检测是否精度丢失
int a = 1024,c=1;
char b = a; //ok, 数据丢失, 类型收窄
char b = { a }; //编译阶段错误,精度丢失
char d=c;//ok,数据不丢失
char d={c};//即使数据不丢失,编译也会报错,因为精度发生了丢失。
基于范围的for循环
for (int &tmp : a)//tmp作为别名,可以改变数组里的值
{
tmp = 2 * tmp;
cout << tmp << ", ";
}
for (int tmp : a)//值传递,tmp不能改变数组里的值
{
cout << tmp << ", ";
}
不能循环传参进来的数组里的值,因为传进来的是数组的地址。
void func(int a[]) //形参中的数组不是数组,是指针变量,无法确定元素个数
{
//基于范围的for, 这个范围要确定后,才能使用
for (int & tmp: a) //err,
{
cout << tmp << endl;
}
}
静态断言
static_assert(sizeof(void *) == 4, "64位系统不支持");
枚举类型:枚举元素的作用域是全局的,所以其他枚举就不能使用其枚举元素名了
int main(void)
{ //Ok、Error都是int类型,而且status中的枚举元素不能再次作为其他枚举的元素了
enum Status {Ok, Error=‘4’};//Error是int类型
// “Ok”: 重定义;以前的定义是“枚举数”
//enum Status2 { Ok, Error };
Status flag = Ok;
cout << sizeof(Ok)<<flag << endl; //4
return 0;
}
强枚举类型:枚举元素的作用域只在强枚举类型中,所以其他强枚举可以使用相同的枚举名。
int main()
{
//强类型枚举, enum后面加上class或struct修饰
enum class Status { Ok, Error };
enum struct Status2 { Ok, Error };
//Status flag = Ok; //err, 必须枚举类型的作用域
Status flag = Status::Ok; //ok
//强类型枚举,可以指定成员变量的类型
enum struct Status3: char { Ok, Error };//指定枚举元素的类型
cout << sizeof(Status3::Ok) << endl;
enum struct Status4: long long { Ok, Error };
cout << sizeof(Status4::Ok) << endl;
system("pause");
return 0;
}
常量表达式constexpr:修饰函数,变量,在编译阶段就得到结果,修饰函数时,函数里只能有typedef, using指令,静态断言和return;也就是说,被constexpr修饰的变量和函数里不能存在不能在编译阶段得到结果的变量。
constexpr int GetNum3()
{//常量表达式,发生在编译阶段
//允许包含typedef, using指令,静态断言
static_assert(1, "fail");
return 3;
}
int main()
{
//error: enumerator value for 'e1' is not an integer constant
//枚举成员初始化,必须是整型常量
enum {e1=GetNum3(), e2}; //ok
constexpr int tmp = GetNum3(); //ok, 发生在编译阶段
enum {a1 = tmp, a2};//ok
return 0;
}
原生字符串
cout << R"(hello, \n world)" << endl;//翻译字符不会翻译
3.类
继承构造,在子类中using A::A,使用父类的构造函数,子类中不能在写自己的构造函数。
//基类
class A
{
public:
A(int x, int y)
{
a = x;
b = y;
}
protected:
int a;
int b;
};
//派生类
class B:public A
{
public:
//继承构造
using A::A;
void display()
{
cout << "a = " << a << ", b = " << b << endl;
}
//没有增加新的成员变量
int tmp;
};
int main()
{
//派生类对象
B obj(10, 20);
obj.display();
return 0;
}
委托构造
class Test
{
public:
//委托构造,一定要通过初始化列表方式
Test():Test(1, 'a')
{
}
Test(int x): Test(x, 'b')
{
}
Test(char x): Test(11, x)
{
}
int a;
char b;
private:
Test(int x, char y): a(x), b(y)
{
}
};
final和override
virtual void func() final {} //这是最终版本的虚函数,不能再重写
//在重写虚函数地方,加上override, 要求重写的虚函数和基类签名(函数名和形参数和返回值)一模一样,其作用就是怕程序员把函数名和形参数和返回值给写错了。
virtual int func(int b) override{ }
C++ 的类有四类特殊成员函数,它们分别是:默认构造函数、析构函数、拷贝构造函数以 及拷贝赋值运算符。
C++11 标准引入了一个新特性:"=default"函数。程序员只需在函数声明后加上“=default;”, 就可将该函数声
明为 "=default"函数,编译器将为显式声明的 "=default"函数自动生成函数 体
class X
{
public:
X() =default; //让编译器提供一个默认的构造函数,效率比用户写的高
X(int i)
{//写了带参的构造函数,编译器不会提供无参的构造函数
a = i;
}
int a;
};
delete禁止函数使用 全局函数,类成员函数均可使用。
class X
{
public:
X() {} //普通无参
X(const X &)=delete; //拷贝构造, 用"=delete"修饰,此函数被禁用
X & operator=(const X &)=delete; //赋值运算符重载函数, 用"=delete"修饰,此函数被禁用
X(int) =delete; //用"=delete"修饰,此函数被禁用
void *operator new(size_t) =delete;
void *operator new[](size_t) =delete;
};
模板的默认参数,类模板默认参数必须从由向左写。
//1、类模板支持默认的模板参数
template<class T, class T2=int> //类模板的模板参数必须是从右往左
class A{};
//2、C++11才支持,函数模板带默认的模板参数
// 函数模板的模板参数可以是从右往左,也可以是从左往右
template<class T=int, class T2> void func2(T a, T2 b){}
//可变参数的模板函数
template<class ... T> //T叫模板参数包
void func(T... args)//args叫函数参数包
{ //获取可变参数的个数
cout << "num = " << sizeof...(args) << endl;}
int main()
{
func<int>(10);
func<int, int>(10, 20);
func<char, int>(10, 'a');
func<char, char *, int>('a', "abc", 250);
return 0;
}
/递归终止函数2
template<class T>
void debug(T tmp)
{
cout << "tmp = " << tmp << endl;
}
//可变参数的模板函数
//参数包展开函数
template<class T1, class ... T2>
void debug(T1 first, T2... last)
{
cout << first << endl;
//递归调用函数本身
debug(last...);
}
int main()
{
debug(1, 2, 3, 4);
/*
函数递归调用过程:
debug(1, 2, 3, 4);
debug(2, 3, 4);
debug(3, 4);
debug(4);
*/
return 0;
}
3.右值和移动函数
右值表示字面常量、表达式、函数的非引用返回值等,也就是不能取地址的值
左值引用不能引用常数,const修饰的数和const int&类型
int a;
const aa=1;
int &b = a; //ok
// int&bb=aa;//err
//int &c = 1; //err
int &s=a;
const int &d = a; //ok
const int &e = 1; //ok
const int &f = func(); //ok
const int tmp = 10;
const int &g = tmp;
//int& bbb=g;//err
//const int & 万能引用
//右值引用
int && a = 10;
int && b = func02();//返回值不是引用的函数属于右值
int && c =a;//右值引用右值引用
int i = 10;
int j = 20;
int && c = i+j;//i+j相当于函数调用的返回值
int k = 10;
int && d = k; //err, 把一个左值赋值给一个右值引用
int a = 10; //a为左值
//int && b = a; //err, 左值不能绑定到右值引用
int && c = std::move(a); //std::move将一个左值转换为右值
转移构造函数和转移赋值函数,通过浅拷贝将临时对象被新的指针指向,这样函数返回值就不会被delete掉。
class MyString
{
public:
MyString(const char *tmp = "abc")
{//普通构造函数
len = strlen(tmp); //长度
str = new char[len+1]; //堆区申请空间
strcpy(str, tmp); //拷贝内容
cout << "普通构造函数 str = " << str << endl;
}
MyString(const MyString &tmp)
{//拷贝构造函数
len = tmp.len;
str = new char[len + 1];
strcpy(str, tmp.str);
cout << "拷贝构造函数 tmp.str = " << tmp.str << endl;
}
//移动构造函数
//参数是非const的右值引用
MyString(MyString && t)
{
str = t.str; //拷贝地址,没有重新申请内存
len = t.len;
//原来指针置空
t.str = NULL;
cout << "移动构造函数" << endl;
}
MyString &operator= (const MyString &tmp)
{//赋值运算符重载函数
if(&tmp == this)
{
return *this;
}
//先释放原来的内存
len = 0;
delete []str;
//重新申请内容
len = tmp.len;
str = new char[len + 1];
strcpy(str, tmp.str);
cout << "赋值运算符重载函数 tmp.str = " << tmp.str << endl;
return *this;
}
//移动赋值函数
//参数为非const的右值引用
MyString &operator=(MyString &&tmp)
{
if(&tmp == this)
{
return *this;
}
//先释放原来的内存
len = 0;
delete []str;
//无需重新申请堆区空间
len = tmp.len;
str = tmp.str; //地址赋值
tmp.str = NULL;
cout << "移动赋值函数\n";
return *this;
}
~MyString()
{//析构函数
cout << "析构函数: ";
if(str != NULL)
{
cout << "已操作delete, str = " << str;
delete []str;
str = NULL;
len = 0;
}
cout << endl;
}
private:
char *str = NULL;
int len = 0;
};
forward保持传入参数的int& int&&和const int&属性
MyString func() //返回普通对象,不是引用
{
MyString obj("mike");
return obj;//我们创建的obj是局部的,在函数返回后就会被销毁掉,
//其实我们返回的时候是拷贝了一个新的临时对象,我们接收临时对象时,
//可以使用右值引用接收,这样匿名对象就不会被销毁掉。
}
int main()
{
MyString &&tmp1 = func(); //右值引用接收,函数返回值是右值,调用移动构造函数
MyString tmp("abc"); //实例化一个对象
tmp = func();//调用移动赋值函数
return 0;
}
template<class T> void func(const T &)
{
cout << "const T &" << endl;
}
template<class T> void func(T &)
{
cout << "T &" << endl;
}
template<class T> void func(T &&)
{
cout << "T &&" << endl;
}
template<class T> void forward_val(T &&tmp) //参数为右值引用
{
//std::forward保存参数的左值、右值属性
func( std::forward<T>(tmp) ); //定义
}
int main()
{
int a = 0;
const int &b = 1;
//需要给forward_val()重载2个版本, const T &, T &
forward_val(a); //"T &"
forward_val(b);//const T &,
forward_val(111);//T &&,虽然const int&也会引用常数,但我们优先调用形参类型是int&&的函数
return 0;
}
4.智能指针
unique指针
unique_ptr<int> up1(new int(11));
unique_ptr<int> up2 = std::move(up1);
cout << "*up2 = " << *up2 << endl;
unique_ptr<int> up1(new int(11));
//释放控制权,不释放堆区内存
int * p = up1.release();
cout << *p << endl;
//cout << *up1 << endl; //err
delete p;
unique_ptr<Test> up2(new Test); //无需释放,自动释放
//人为指定释放堆区空间
up2 = nullptr; //1
up2 = NULL; //2
up2.reset();
up2.reset();释放智能指针其实就是把堆区的指针数变成零,所以我们多次将指针指向null也不会报错,因为系统会当指针数为0的时候就释放内容了。
shared_ptr
shared_ptr<int> sp1(new int(11));
shared_ptr<int> sp2 = sp1; //拷贝构造, 有2个对象和堆区空间绑定
cout << "num = " << sp2.use_count() << endl; //打印计数器
//释放sp1, 只是计数器减去1, 堆区空间没有释放
sp1.reset();
cout << "num = " << sp2.use_count() << endl; //打印计数器
cout << *sp2 << endl;
//cout << *sp1 << endl;//err, 释放sp1和堆区空间的绑定,无法通过sp1操作堆区内容
//释放sp2, 只是计数器减去1, 当计数器的值为0, 堆区空间就是释放
sp2.reset();
cout << "num = " << sp2.use_count() << endl; //打印计数器
weak_ptr:
weak_ptr<int> wp = p1;
wp.use_count();
wp.lock(); //当堆区指针数为0时,释放堆区,wp.lock()改指向null
int main()
{
shared_ptr<int> p1(new int(11));
shared_ptr<int> p2 = p1; //有2个对象绑定堆区内容
weak_ptr<int> wp = p1;
cout << "num = " << p1.use_count() << endl; //打印计数器
cout << "num = " << wp.use_count() << endl; //打印计数器
//weak_ptr虽然不和堆区空间绑定,可以通过lock函数获取shared_ptr<int>对象
shared_ptr<int> p3 = wp.lock(); //有3个对象绑定堆区内容
cout << "num2 = " << p1.use_count() << endl; //打印计数器
cout << "num2 = " << wp.use_count() << endl; //打印计数器
cout << *p1 << ", " << *p2 << ", " << *p3 << endl;
//cout << *wp << endl; //err, 没有重载 * 和 ->
p1.reset();
p2.reset();
p3.reset();
cout << "num3 = " << p1.use_count() << endl; //打印计数器
cout << "num3 = " << wp.use_count() << endl; //打印计数器
//当堆区空间释放后,wp.lock()获取的返回值为nullptr
shared_ptr<int> tmp = wp.lock();
if(tmp == nullptr)
{
cout << "堆区空间已经释放\n";
}
return 0;
}
5.函数绑定
#include <iostream>
#include <functional> //std::function
using namespace std;
//1、普通函数
void func()
{
cout << __func__ << endl;
}
//2、类中静态函数
class Test
{
public:
static int test_func(int a)
{
cout << __func__ << "(" << a << ") ->: ";
return a;
}
};
//3、类中的仿函数
class MyFunctor
{
public:
int operator()(int a)
{
cout << __func__ << "(" << a << ") ->: ";
return a;
}
};
int main()
{
//1、绑定普通函数
function<void(void)> f1 = func;
f1(); //等价于 func()
//2、绑定类中的静态函数
function<int(int)> f2 = Test::test_func;
cout << f2(10) << endl; // Test::test_func(10)
//3、绑定类中的仿函数,绑定对象, 仿函数调用obj()
MyFunctor obj;
function<int(int)> f3 = obj;
cout << f3(22) << endl;
return 0;
}
void func(int x, int y)
{
cout << x << " " << y << endl;
}
int main()
{
bind(func, 11, 22)(); //输出11 22
//std::placeholders::_1, 函数调用时,被第一个参数替换
//std::placeholders::_2, 函数调用时,被第二个参数替换
//输出11 22
bind(func, std::placeholders::_1, std::placeholders::_2)(11, 22);
using namespace std::placeholders;
bind(func, 11, _1)(22, 33, 44); //输出 11 22, 后面的33, 44没有作用
bind(func, _2, _1)(11, 22); //输出 22 11
//bind(func, _2, 22)(11); //err, 没有第2个参数
bind(func, _2, 22)(11, 0); //0 22
bind(func, _3, 22)(11, 1, 3); //3 22
return 0;
}
class Test
{
public:
void func(int x, int y)
{//成员函数
cout << x << " " << y << endl;
}
int a; //成员变量
};
int main()
{
Test obj; //创建对象
//绑定成员函数
function<void(int, int)> f1 = bind(&Test::func, &obj, _1, _2);
f1(11, 22); //obj.func(11, 22)
//绑定成员变量
function<int &()> f2 = bind(&Test::a, &obj);
f2() = 111; //obj.a = 111;
cout << "obj.a = " << obj.a << endl;
return 0;
}
6.lambda
空。没有使用任何函数对象参数。
=。函数体内可以使用 lambda 所在作用范围内所有可见的局部变量(包括 lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局 部变量)。
&。函数体内可以使用 lambda 所在作用范围内所有可见的局部变量(包括 lambda 所在类的 this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所 有局部变量)。
this。函数体内可以使用 lambda 所在类中的成员变量。
a。将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝, 因为默认情况下函数是 const 的。要修改传递进来的 a 的拷贝,可以添加 mutable 修饰符。
&a。将 a 按引用进行传递。
a, &b。将 a 按值进行传递,b 按引用进行传递。
=,&a, &b。除 a 和 b 按引用进行传递外,其他参数都按值进行传递。
&, a, b。除 a 和 b 按值进行传递外,其他参数都按引用进行传递。
int tmp = 1;
class Test
{
public:
int i = 0;
void func()
{
int a = 10;
//err, []为空,没有捕获任何变量
//auto f1 = [](){ cout << i << endl; };
auto f1 = [=]()
{
cout << i << endl;
cout << tmp << endl;
};
auto f2 = [&](){ cout << i << endl; };
//只是捕获类成员变量、全局变量, 不能捕获局部变量
auto f3 = [this]()
{
cout << i << endl;
cout << tmp << endl;
//cout << a << endl; //err
};
}
};
int main()
{
int a = 0;
int b = 0;
int c = 0;
auto f1 = [](){ };
//a, b以值传递方式
auto f2 = [a, b](){ cout << a << ", " << b << endl; };
auto f3 = [a, b](int x, int y)
{
cout << a << ", " << b << endl;
cout << "x = " << x << endl;
cout << "y = " << y << endl;
};
//f3(10, 20);
//以值传递方式传给lambda表达式
auto f4 = [=]{cout << a << ", " << b << endl;};
//以引用方式捕获外部的变量
auto f5 = [&]{cout << a << ", " << b << endl;};
//a以值传递, 其它以引用方式传递
auto f6 = [&, a]{cout << a << ", " << b << endl;};
//a以引用传递,其它值传递
auto f7 = [=, &a]{cout << a << ", " << b << endl;};
//默认情况下,lambda函数,以const修饰函数体, 值传递无法修改, 想修改加mutable
auto f8 = [=]() mutable
{
a++;
cout << tmp << endl;
};
return 0;
}
//仿函数,重载operator()
class MyFunctor
{
public:
MyFunctor(int i): r(i) {} //构造函数
//仿函数,重载operator()
int operator() (int tmp)
{
return tmp+r;
}
private:
int r;
};
int main()
{
int tmp = 10;
//仿函数的调用
MyFunctor obj(tmp); //调用构造函数
//调用仿函数
cout << "result1 = " << obj(1) << endl;
//仿函数是编译器实现lambda的一种方式
//lambda表达式
auto f = [&](int t)
{
return tmp+t;
};
cout << "result2 = " << f(1) << endl;
return 0;
}
//使用 std::function 和 std::bind 来存储和操作 lambda 表达式
function<int(int)> f1 = [](int a) { return a; };
function<int()> f2 = bind([](int a){ return a; }, 123);
lambda优势
for_each(largeNums.begin(), largeNums.end(),
[](int &n)
{
cout << n << ", ";
}
);
cout << endl;
7.类型转换
char a = 'a';
int b = static_cast<char>(a);//正确,将char型数据转换成int型数据
double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针
int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据
const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
if(Derived *dp = static_cast<Base *>(bp)){//下行转换是不安全的
//使用dp指向的Derived对象
}
else{
//使用bp指向的Base对象
}
if(Base*bp = static_cast<Derived *>(dp)){//上行转换是安全的
//使用bp指向的Derived对象
}
else{
//使用dp指向的Base对象
}
const_cast将常量指针常量引用转换成非常量指针引用:在通过非常量指针修改内存值。
①常量指针被转化成非常量的指针,并且仍然指向原来的对象;
②常量引用被转换成非常量的引用,并且仍然指向原来的对象;
③const_cast一般用于修改底指针。如const char *p形式。
const int g = 20;
int *h = const_cast<int*>(&g);//去掉const常量const属性
*h=40;//*h是非常量指针,指向原来的内存,所以可以改变其指向内存的值
const int g = 20;
int &h = const_cast<int &>(g);//去掉const引用const属性
const char *g = "hello";
char *h = const_cast<char *>(g);//去掉const指针const属性
dynamic_cast
Derived *dp = dynamic_cast<Base *>(bp)
父类指针强转成子类且父类存在虚函数时,建议使用dynamic,可以检测是否存在越界的风险
reinterpret_cast强转
int n=1;
int *p=(int*)n;
int *p=reinterpret_cast<int*>(n);
Derived *dp=reinterpret_cast<Base*>(bp);
8.字符串和数值转换
string pi = "pi is " + to_string(3.1415926);
int intpi = stoi(pi);