读书笔记(二十六) 《C++ Primer》#2
背景:
我为什么要重学C++?第一是巩固核心,软件编程有三大核心,语言、操作系统、框架设计,语言首当其冲,核心能力大都看不见摸不着只有你自己知道的东西。第二是将分散的知识串联起来,这样就能产生新的知识和新的创意。第三是当我们在巩固时创造出了新的知识,那么这些新的知识和旧的知识将同时变成智慧融合到身体中。
本系列基于《C++ Primer》学习,由于重点放在“重学”,我略过了滚瓜烂熟的部分挑出以前常忽略的部分,以及记忆没有那么深刻的部分,特别是那些重要的但没有上心的部分。
开始
1.const限定
关键字const对变量的类型加以限定,因此它的值在初始化后不能被改变。
const默认情况下只在文件内生效,当需要支持多个文件共享时,则使用extern关键字,不管定义和声明都添加extern关键字,并且只需要定义一次。
//a.cpp
extern const int test_constVar = fcn();
//b.cpp
extern const int test_constVar;
通过添加extern共享const变量。
const引用(常量引用)
与普通引用不同的是,常量引用不能被用作它修改它所绑定的对象。
const int ci = 10;
const int &r1 = ci;
r1 = 40; //错误,常量引用不能修改绑定对象
int &r2 = ci; //错误,普通引用无法赋值常量
严格来说,并不存在常量引用。因为引用不是一个对象,所以我们没法让引用本身恒定不变。 事实上,由于c++语言并不允许随意改变引用所绑定的对象,所以从这层意义上理解所有的引用又都算是常量。 引用的对象是常量还是非常量可以决定其所能参与的操作,却无论如何都不会影响到引用和对象的绑定关系。 简单来说,由于绑定对象无法改变,所以要求引用对象也不能改变。
但const引用可以绑定一个非const对象,其意义为,绑定后const引用无法改变,只能由所绑定的对象来决定内容。
const指针(常量指针)
const指针与const引用有同样的规则。 const指针在初始化后无法改变,const指针不能改变所指对象的值,但可以指向非常量变量。 当指向非常量时,const指针所指向的对象的值,只能由对象本身来决定。
C++11中,用constexpr来表示常量表达式,声明为constexpr的变量是一个常量,编译器会验证变量的值是否为常量表达式并离线计算该值。
constexpr int mf = 20 + 1; //离线计算
constexpr int limit = mf + 1; //离线计算
constexpr int sz = size(); // size()必须是constexpr函数
2.类型别名
类型别名是某种类型的同义词。
使用类型别名是为了让复杂的类型名字变得简单明了、易于理解和使用。
C++11新标准中,使用using来定义类型的别名
typedef double wages; //wages是double的同义词
typedef wages base, *p; //base是double的同义词,并且p是double*的同义词
typedef char *pstring; //pstring 是char*的同义词
using It = Item_test; // It 是Item_test的同义词
//定义
wages test1; //定义double 变量
It item; //定义Item_test 变量
pstring cstr = 0; //定义一个char *
3.其他
auto和decltype都是用来让编译器在离线下自动识别类型的关键字。 其中decltype着重识别结果类型与表达式的关系。
预处理器能确保头文件多次包含仍能安全工作,它在编译之前被执行。
#include、#define、#ifdef、#ifndef都是预处理关键字。
第3章
1.string字符串类
前面说using可以用于类型别名,using也可以用于声明命名空间。 using namespace::name 不过头文件中不应包含using声明,因为这会导致头文件在被拷贝时多次重复声明using。
string是标准库中的类对象。 string在使用中最容易发生的问题就是拷贝。 特别是等号(=、+)引起的合并和拷贝需要注意。 string在比较时(==、!=、<、<=、>、>=)有比较算法但每个字符都会比较。
严格来说string对象不属于容器类型,但string支持很多与容器类似的操作,比如下标、迭代器等。
2.vector动态数组
vector是标准中的动态数组模版库。 模版本身不是类或函数,可以把模版看成编译器生成的类或函数。编译器根据模版创建类或函数的过程称为实例化。因此使用模版时,我们要指出编译器应把类或函数实例化成何种类型。 因此vector是模版而非类型,由vector生成的类才叫类型。
vector容器本身就是对象,因此也能通过拷贝初始化。
vector<int> v1 = {1,2,3}; // 初始化3个元素,1、2、3
vector<int> v2(10,-1); // 初始化10个元素,都是-1
vector<int> v3 = v1; // 用拷贝来初始化,将v1中的数据拷贝到v3,完成后v3拥有独立的数据
vector<int> v4(20); // 提前预备20个值,每个值都是0
由于vector是动态数组,因此如果能在初始化时提前告知vector的话,vector就不用扩容了,运行时性能会更好。
3.迭代器
迭代器有三种不同含意,一是迭代器概念本身,二是容器定义的迭代器类型,三是指某个迭代器对象。
有时她跟指针很像,但又完全不一样。
其实迭代器是个由模版封装过的类,根据不同的类型的容器,编译器生成功能相似但命名不同的类。
迭代器又重写了==、>、<、!=、<=、>=、+=、-=运算符,使得我们在编写代码时更加方便。
共识:当使用迭代器时如果对容器做了增删操作,则会使得迭代器失效,甚至报错和崩溃。
迭代器对象是实时生成的,当我们获取迭代器对象时,容器会实时生成一个迭代器对象,我们再通过操作这个迭代器对象达成我们的目的。
4.数组
一些复杂的数组声明难以理解,因此如果能从数组的名字开始按照由内向外的顺序阅读会更加容易些。
int arr[10]; // 普通数组含有10个整数
int *ptrs[10]; // ptrs是含有10个整数指针的数组
int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; // arrRef引用一个含有10个整数的数组
int *(&arry)[10] = ptrs; //arry是数组的引用,该数组含有10个整数指针
数组真正使用的时候编译器会把它转成指针。 因此指向数组的指针可以使用+、-、==、!=运算符号来操作数组。
int arr[] = {0,1,2,3};
int *p = arr;
++p;
int *e = &arr[4]; // e指向arr元素的下一位置
for(int *b = arr; b!=e ; ++b)
cout << *b << endl;
至于多维数组,严格来说C++中没有多维数组,通常所说的多维数组其实是数组的数组,也就是用数组类型组成的数组。
其中要注意的是在多维数组遍历时,其遍历顺序应该按照数组的整块内存来遍历,否则命中效率比较低。
感谢您的耐心阅读
Thanks for your reading
版权申明
本文为博主原创文章,未经允许不得转载:
Copyright attention
Please don't reprint without authorize.
微信公众号,文章同步推送,致力于分享一个资深程序员在北上广深拼搏中对世界的理解
QQ交流群: 777859752 (高级程序书友会)