C/C++11的小语法与知识点
1.auto
-
在c/c++早期的时候,auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量
-
auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器。auto声明的变量必须由编译器在编译时期推导而得的
//必须在新的编译器才可以跑过,旧的会出错
#include <iostream>
using namespace std;
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;\\b是int类型
auto c = 'a';\\c是char类型
auto d = TestAuto();\\d是int类型
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}
【注意】:
使用auto定义变量时必须对其进行初始化,在编译阶段编译器根据初始化表达式来推导auto的实际类型,因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”。编译器在编译期间会将auto替换为变量实际的类型。
1.1 auto与指针连用
auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
#include <iostream> using namespace std; int main() { int x = 10; auto a = &x;//int* auto* b = &x;//int* auto& c = x;//int,引用也是x,所以类型也是int cout << typeid(a).name() << endl; cout << typeid(b).name() << endl; cout << typeid(c).name() << endl; *a = 20; *b = 30; c = 40; return 0; }
在同一行定义多个变量
当在同一行定义多个变量时,这些变量必须是相同类型的,否则编译器会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
void TestAuto() { auto a = 1, b = 2; auto c = 3, d = 4.0;//改行代码会编译失败,因为c和d的初始化表达式类型不一样 }
auto不能推导的场景
1.auto不能作为函数的参数
//此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导 void TestAuto(auto a) { }
2.auto不能直接用来声明数组
void TestAuto() { int a[] = { 1, 2, 3 }; auto b[] = { 1, 2 }; }
3.auto不能定义类的非静态成员变量
4.实例化模板时不能使用auto作为模板参数
2. nullptr和nullptr_t
为了考虑兼容性,c++11并没有消除常量0的二义性,c++11给出了全新的nullptr表示空值指针,c++11为什么不在NULL的基础上进行扩展,这是因为NULL以前就是一个宏,而且不同的编译器商对于NULL的实现可能不太相同,而且直接扩展NULL,可能会影响以前旧的程序,因此,为了避免混淆,c++11提供了nullptr,即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型是nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件中:
typedef decltype(nullptr) nullptr_t;
【注意】:
在使用nullptr表示指针空值的时候,不需要包含头文件,因为nullptr是c++11作为关键字引入的
在c++11中,sizeof(nullptr) 和sizeof(void* 0)所占字节数相同
为了提高代码的健壮性,在后续表示空值指针是建议最好使用nullptr
3. 在linux下显式的使用c++11
-
对于一般的linux下的g++编译器都是支持c++98的,如果想让其支持c++11编译的话,那就只需要显式打开c++11就好了
#include <iostream> #include <typeinfo> using namespace std; int TestAuto() { return 10; } class Test { public: Test(int a = 0) : _a(a) {} private: int _a = 1;//c++11支持对于非静态成员变量直接进行初始化 }; int main() { int a = 10; auto b = a; auto c = 'a'; auto d = TestAuto(); Test e; auto f = e; cout << typeid(b).name() << endl;//int cout << typeid(c).name() << endl;//char cout << typeid(d).name() << endl;//int cout << typeid(e).name() << endl;//class Test cout << typeid(f).name() << endl;//class Test //auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化 return 0; }
对于上面的代码,具有c++11的新语法auto,如何对其进行编译呢?
g++ -o auto auto.cpp //采用这种编译的话,就显示 //‘b’ does not name a type //表示该编译器不支持c++11,所以就要显式的使用c++11编译 g++ -std=c++11 -o auto auto.cpp //这种进行编译的话就不会出错,显式的使用c++11
4. typeinfo 库
包含typeid().name()工具函数,用来返回变量的数据类型,学习这个函数
typeid操作符,其返回结果为名为type_info的标准库类型的对象的引用。type_info中存储特定类型的有关信息,定义在typeinfo头文件中
typeid().name(),用来获取表达式的类型,以c-style字符串形式返回引用名。
【注意】:对于非引用类型,typeid().name()在编译时期识别,只有引用类型才会在运行时识别。
例:
#include <iostream> #include <typeinfo> using namespace std; int TestAuto() { return 10; } class Test { public: Test(int a = 0) : _a(a) {} private: int _a = 1;//c++11支持对于非静态成员变量直接进行初始化 }; int main() { int a = 10; auto b = a; auto c = 'a'; auto d = TestAuto(); Test e; auto f = e; cout << typeid(b).name() << endl;//int cout << typeid(c).name() << endl;//char cout << typeid(d).name() << endl;//int cout << typeid(e).name() << endl;//class Test cout << typeid(f).name() << endl;//class Test //返回值是char const *,表示该函数的返回值是一个字符串(字符指针) cout << typeid(typeid(f).name()).name() << endl;//char const * //auto g; 无法通过编译,使用auto定义变量时必须对其进行初始化 return 0; }
【面试题】:
对于可以使用typeid().name()函数的返回值做为类型名进行定义吗?
不可以
由于typeid().name()函数返回的是一个字符串,肯定不能用于定义
5. 内置类型
即某个语言初期的时候定义的类型就叫做内置类型
6. delete函数(c++11)
//声明该函数,但是不需要进行实现
HeapType (const HeapType& ht) = delete;
7. 匿名对象
//生命周期只有一行
Date d1;//正常对象
Date();//这就是匿名对象
8. 定制化的为一个类重载operator new和operator delete
//如果你重载了operator new和operator delete
//那么该类使用new的时候就会使用你写的operaor new和operator delete
void* operator new(size_t sizt) = delete;
viod operator delete(void* p) = delete;
//没有禁用掉全局和静态的对象产生
//对于其他对象new时,不会影响其他对象的new
9. size_t
无符号整数类型
size_t是sizeof运算符返回的类型,广泛应用于标准库中以表示大小和计数
10. C++不加.h
对于C++加不加.h,其实都是可以的,C++的标准是不需要加.h的,比如C语言中的stdio.h和string.h,在C++中就是stdio和string或者cstdio和cstring。加了C之后表示该头文件是从C语言演变过来的,对于C的那是字符串函数,而对于C++那是string对象
11. getline的用法
//从流cin中读取数据,写到str中,直到遇到delim istream& getline(istream& cin, string& str, char delim); //从流中读取数据写到str,默认遇到空格或者换行终止 istream& getline(istream& cin, string& str);
【作用】
对于该全局函数,包含在string头文件中
从流中读取数据,是不会读取到分隔符,只会讲终止符前面的数据读取到str中
12. cin.getline的用法
//默认的分隔符为空格或者换行 istream& getline(char* s, streamsize n);//streamsize是有符号字符大小 //分隔符为delim istream& getline(char* s, streamsize n, char delim);
【作用】
对于该函数是属于cin的
直接从cin中读取字符,直到遇到分隔符停止,或者已经将n个字符写入到s中,实际是读入n-1个字符,最后一个是终止符。(包括终止定界符)
13. 为什么main函数会有返回值
平常看到的mian函数都会有着自己的类型,都是int类型
int main() { return 0; }
对于main函数为什么要有着自己的返回值
由于main函数也是一个进程运行起来的话,对于一个进程当其运行的时候,那么该进程肯定就会有着自己的父进程,那么当main函数结束的时候,父进程就要知道main函数的退出状态,所以就要有着返回值了。对于这个返回值一般都是0,返回0的话,默认该进程执行成功,正确返回。
所以main函数的返回值一般都是0(return 0)
如果不写return 0的话,那么对于c99之后的编译器都会自己默认返回0
14. 测试vector的增容情况
//测试vector自我开辟容量的 #include <iostream> #include <vector> int main() { std::vector<int> varray; size_t sz = varray.capacity(); std::cout << "test vector capacity!" << std::endl; for (size_t i = 0; i < 1000; i++) { varray.push_back(i); if (varray.capacity() != sz) { sz = varray.capacity(); std::cout << "capacity change:" << sz << '\n'; } } return 0; }
对于在g++下和VS2017下STL库中的vector容器增容的方法是不一样的:
对于g++下:(SGI版本的STL)
vector是按照2倍增容的
对于VS下:(PJ版本的STL)
vector是按照1.5增容的
15. 对于for+auto遍历容器
对于使用for+auto语法糖 遍历一个容器的时候需要什么东西呢?
首先肯定是需要迭代器的。
但是都需要什么迭代器呢?
今天突然写到这个的一个代码。就是对于vector容器的自我实现
由于vector容器实现的时候需要三个模板指针,_start, _finish, _endofstorage
这三个模板类型的指针,分别代表的意思是
_start:vector容器的起始地址
_finish:vector容器所包含大小的下一个地址也就是, _start+size();
_endofstorage:vector容器的最大容量的下一个地址, _start + capacity();
那么我写了这个的一个代码。写了一个赋值运算符重载
我写的时候,忘了对于_finish进行增加,导致最后的时候,_finish和 _satrt是相等的,都指向该vector容器的开头,
然后我再用三种不同的方法进行遍历:
for+auto(需要使用迭代器)
for+[](不需要使用迭代器)
for+iterator(需要使用迭代器)
这三种方法分别遍历:
for + iterator遍历和for+auto进行出错。对于for + [],这个肯定是正确的
说明对于for+iterator和for+auto都是需要begin()迭代器和end()迭代器
16. 在一个类型后面直接加上()表示啥意思
对于这个()表示的的意思就是创建了一个对象(匿名对象)。并且将该对象初始化为0.
17. std::array
std::array跟数组没有多大的区别。也就是不可增容的数组
std::array相对于数组没有太大的区别,增加了迭代器等函数
#include <iostream>
#include <array>
int main()
{
std::array<int, 4> array = { 1, 2 , 3, 4 };
for (auto e : array)
{
std::cout << e <<std::endl;
}
int array_size = sizeof(array);
std::cout << array_size << std::endl;
return 0;
}
18. std::function封装可执行对象
std::bind和std::function是从boost中移植进来的C++新标准,这两个语法使得封装可执行对象变得简单而易用。
std::bind和std::function也可以结合lambda表达式一起使用
std::function < int (int, int) >表示std::function封装的可执行对象返回值和两个参数均是int类型
【注意】:对于使用std::function来封装一个执行体对象的时候,必须要包含头文件
#include <functional>
可以封装的执行体:普通函数,Lamda表达式,仿函数,函数指针以及类成员函数和类静态成员函数
std::function对C++中可执行实体进行封装,形成一个新的可调用的std::function对象
原型:
template <class R, class ... Args>
class function<R(Args...)>
R是返回值类型,Args是函数的参数类型
19. std::bind
将要调用的对象和调用对象的参数进行绑定,返回新的可调用对象(std::function类型,参数列表可能改变),返回新的std::function可调用对象的参数列表根据bind函数实参中的std::placeholders::_x从小到大对应的参数确定
#include <iostream>
using namespace std;
class A
{
public:
void fun_3(int k,int m)
{
cout<<k<<" "<<m<<endl;
}
};
void fun(int x,int y,int z)
{
cout<<x<<" "<<y<<" "<<z<<endl;
}
void fun_2(int &a,int &b)
{
a++;
b++;
cout<<a<<" "<<b<<endl;
}
int main(int argc, const char * argv[])
{
auto f1 = std::bind(fun,1,2,3); //表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3
f1(); //print:1 2 3
auto f2 = std::bind(fun, placeholders::_1,placeholders::_2,3);
//表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别有调用 f2 的第一,二个参数指定
f2(1,2);//print:1 2 3
auto f3 = std::bind(fun,placeholders::_2,placeholders::_1,3);
//表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别有调用 f3 的第二,一个参数指定
//注意: f2 和 f3 的区别。
f3(1,2);//print:2 1 3
int n = 2;
int m = 3;
auto f4 = std::bind(fun_2, n,placeholders::_1);
f4(m); //print:3 4
cout<<m<<endl;//print:4 说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的
cout<<n<<endl;//print:2 说明:bind对于预先绑定的函数参数是通过值传递的
A a;
auto f5 = std::bind(&A::fun_3, a,placeholders::_1,placeholders::_2);
f5(10,20);//print:10 20
std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2);
fc(10,20);//print:10 20
return 0;
}
20. lamda表达式
lamda就是一个匿名函数
要使用lamda就是要用一个函数指针来接收这个匿名函数的地址,然后再次使用这个函数指针来调用lamda表达式
【注意】:对于lamda的接收可以
-
使用std::function来封装一个执行体对象
-
使用auto语句在编译的时候,自动生成一个符合该类型的变量
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;
}
#include <iostream>
#include <functional>
std::function<void()> myfunc;
class Basic
{
private:
int _num;
char _c;
public:
Basic(): _num(10), _c('c')
{}
void Set()
{
//可以修改该类的成员变量
myfunc = [this]()->void{
_num = 11;
_c = 'b';
};
//必须调用这个函数
myfunc();
}
void Show()
{
std::cout << "_num: " << _num << "\n_c: " << _c << std::endl;
}
};
int main()
{
Basic b;
b.Show();
b.Set();
b.Show();
//std::string s = "hello world!";
//std::function<void ()> myfunc;
//不能捕获所在函数中的变量
//myfunc = []()->void{
// //无法捕获到变量s
// std::cout << s << std::endl;
// //可以输出
// std::cout << "1" << std::endl;
//};
//以值传递的方式捕获
//myfunc = [=]()->void{
// std::cout << s << std::endl;
// //s += "asd"; //出错
//};
//std::cout << s << std::endl;
//以引用的方式捕获
//myfunc = [&]()->void{
// std::cout << s << std::endl;
// //改变了外边的变量
// s += "abc";
// std::cout << s << std::endl;
//};
//myfunc();
//std::cout << s << std::endl;
return 0;
}
[]:中括号用于控制在main函数中,lamda表达式之前的变量在lambda表达式中的访问形式
-
[]:lamda表达式中不能够使用所在函数(main)中的变量
-
[=]:值捕获,lamda表达式中可以以拷贝的方式访问到函数中变量的值
-
[&]:引用捕获,lamda表达式使用所在函数中的变量都是用引用的方式
-
[this]:捕获this指针的成员。也就是lamda表达式必须是在一个类中
(int a, int b):函数的形参
->int:lamda表达式的返回值定义
{}:大括号内为lamda表达式的函数体