一、前言
最近在看C++Primer第5版,先前已经看过第4版,但是发现第5版在整个知识布局与个别知识的讲解上跟第4版差别还是挺大的,尤其是新增了C++11的内容,正如孟岩老师在第5版前言中所讲:“现在能够以新的C++11风格开发实践的人是凤毛麟角,如果能够纯熟的运用C++11的新特征、新机制,那么就能够形成一种简洁优雅的C++编程风络,开发会变得更高效,更高质”。
所以正好借助第5版来重新学习巩固C++的知识。《C++的那些事》这个系列,将会以知识碎片的形式记录我在学习过程中一些知识重点。
二、关于C++的学习
个人身为C++菜鸟,自然是没有任何经验之谈,这里摘录C++Primer5ED前言里一些个人以为很有道理的观点。
1,学习语言的一个境界是把自己想象成编译器。
2,使用C++语言的“两面性”观点。C++正在走向完美,所以,C++语言值得学习(甚至研究)。这些知识可以成为一切编程的基础。然而在实践中,不必全面地使用C++语言的各种特性,而应根据工程项目实际情况,适当取舍(譬如动态类型信息、虚拟继承、异常等特性的使用)。通常只鼓励使用C++语言的一个子集。
关于第2点,个人是相当的认同,C++的一些高级特性,确实只有在一些库例程中才有体现,一般工程开发中很少能用得上。所以在学习C++的时候,开始不必对一些语法细节或高级技法过于追求完美,可以先知其大概,在以后工作学习中再逐渐加深认识。
三、数据与类型
1,任何一门编程语言都是由一些基本组件来构成,C++也是一样。
整型、字符型等内置类型
变量,用来为对象命名
表达式和语句,用于操纵上述数据类型的具体值
if或while等控制结构,这些结构允许我们有选择地执行一些语句或者重复地执行一些语句
函数,用于定义可供随时调用的计算单元
自定义数据类型:比如:类
标准库
C++的另外一点就是编程思想,通过类引入了面向对象编程的思想,再通过模板等技术引入了通用或泛型编程的思想。
这本篇文章中,主要是关于前2点的内容:对象和对象的类型。
2,类型是程序设计中最基本的概念,一种类型不仅定义了数据元素的内容,还定义了这类数据上可以进行的运算。内置的数据类型如此,用户自定义的类型也是如此,C++的一个很重要的任务就是让用户设计的类型像语言内置类型一样好用。
3,程序所处理的数据都保存在变量中,而每个变量都有自己的类型。
4,执行算术运算时,数据类型的选择
short 一般很少使用,容易产生越界。
char类型在一些实现中有时作为signed处理,而有时作为unsigned处理,所以只用char来表示字符是明智之举。
在大多数的时候使用int就没有问题,在很多机器上int用4个字节存储,足够满足大部分计算的要求。
浮点型的选择就很简单了,使用double类型基本不会有什么错误,而且有可能会比float计算代价低。
5,在使用无符号数作为循环的索引时,注意unsinged int 与 int类型间的隐式数据转换,如下面的程序,可能实际并不是你想要的结果。
// error 变量u永远也不会小于0,循环条件一直成立 for (unsigned u = 10; u > = 0; -- - u) { std::cout << u << std::endl; }
6,C++11定义了一种新的指针字面值nullptr,用于取代C语言中预编译常量NULL
7,注意C++中初始化与赋值的区别,这一点在类的定义中更为明显。初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。
8,引用和指针都是C++定义的复合类型,引用与指针的定义是由一个基本数据类型和紧随其后的一个声明符列表组成。
1)引用是给变量另起了一个名字,当定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始化拷贝给引用。一旦初始化完成,程序把引用和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。
实际上引用的本质是指针,而且是一个常量指针,占用4个字节的空间。
2)与引用不同的是,指针本身是一个对象,允许指针拷贝和赋值,而且在指针的生命周期内,它可以先后指向几个不同的对象。
9,常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式。
在一个复杂的系统中,很难分辨一个初始值到底是不是常量表达式。C++11允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明constexpr的变量一定是一个常量,而且必须用常量表达式初始化:
constexpr int mf = 20; constexpr int limit = mf + 1; constexpr int sz = size(); // 只有当size是一个constexpr函数时才是一条正确的声明语句
用constexpr修饰的指针说明是常量指针,它本身在定义初始化后不可以再更改指向,但是所指的对象可以是个变量。
10,注意指针、常量和类型别名在一块的时候:
typedef char* pstring; // pstring是一指向字符的指针 const pstring cstr=0; // cstr是一个指向字符的常量指针 const pstring *ps; // ps是一个指针,它的对象是指向char的常量指针。
11,auto类型
C++11引入auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。编译器是通过初始值来推算变量的类型,显然,auto定义的变量必须有初始值。
如果我们用一个引用类型去初始化一个auto类型时,得到的类型将是引用对象的类型:
int i = 0, &r = i; auto a = r; // a是一个整数
其次auto一般会忽略掉顶层的const,同时底层的const则会保留下来。
const int ci = i, &cr = ci; auto b = ci; // b是一个整数 auto c = cr; // cr是一个整数(ci的顶层const特征被忽略了) auto d = &i; // d是一个指针(指针是指向整数的指针) auto e = &ci; // e是一个指向整数常量的指针
如果希望auto推断出的是auto类型是一个顶层的const,需要明确指出:
const auto f = ci;
12,decltype类型指示符
有的时候想从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量,为些C++11引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型。
decltype(f()) sum = x; // sum的类型就是f的返回值类型,编译器并不调用f
decltype在处理顶层const和引用的方式与auto不同。
const int ci = 0, &cj = ci; decltype(ci) x = 0; // x的类型是const int decltype(cj) y = x; // y的类型是const int&, y绑定到变量x decltype(cj) z; //error, z是一个引用,必须初始化
引用从来都是作为其所指对象的同义词出现,只有用在decltype处是个例外。
delctype((variable))(注意是双层括号)的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用。
13,标准库定义了2种非常重要的抽象类型,一种是string用来支持可变长的字符串;另一种是vector表示可变长的集合。
14,对于string和vector对象都可以有多种方式初始化,这取决于类的定义,但一般直接初始化和拷贝初始化都是存在的。如果使用等号“=”初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化。
string s1 = "hiya"; // 拷贝初始化 string s2("hiya"); // 直接初始化 string s3(10, 'c'); // 直接初始化
15,string类型和标准库容器类型都提供了一种size_type类型,它是一个无符号的值,而且足够存放下任何容器或string对象的大小。而在C++11中,我们可以用auto或decltype来让编译器自动推断出这种类型,而不用写很长的代码,比如:
vector<vector<double>>::size_type i; // 原来的方法 decltype(dvv.size()) i; // C++11用decltype auto i = dvv.size(); // C++11用auto
16,C++11中另外为vector对象提供了一种列表初始化的方法,此时,用花括号括起来的0个或多个初始化元素值被赋予vector对象。但是值得注意的是:如果用的是花括号,可以表述成我们想列表初始化该vector对象。也是就是,初始化过程会尽可能地把花括号内的值当成是元素初始化的列表来处理,只有在无法执行列表初始化时才会考虑其他初始化方式。
vector<string> v1{"hi"}; //v1有一个元素 vector<string> v2("hi"); //error vector<string> v3{10}; //v3本来想用10来初始化vector,但是发现10不是strring对象,所以把v3初始化为10个元素。 vector<string> v4{10,"hi"}; //v4本来想用10和“hi”初始化vector,但发现10不是string对象,所以就把v4定义为10个元素的vector
17,数组,与vector类似,数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在的位置访问。而且数组是在定义的时候就分配了大小,中间不能变化,这就给使用时带来很大的不便。
18,指针其实与迭代器具有相同的功能,在遍历数组的情况下,用指针操作就像在容器上使用迭代器一样。类似于容器类型的begin和end迭代器,C++11定义了两个函数begin和end用来获取序列的首指针和尾指针,这两个函数定义在iterator头文件中。
int ia[]={0,1,2,3,4,5,6,7,8,9}; int *beg = begin(ia); // 指向ia首元素的指针 int *end = end(ia); // 指向ia尾元素的下一位置的指针