《C++ Primer》学习笔记

第2章 变量和基本类型

  • 什么是对象: 内存中具有类型的区域。
  • 初始化不是赋值: 初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。
  • extern: 当碰到它搞不清楚时,想一想声明和定义的关系,声明可以有多份,但定义必须有且只能有一份。
  • const 和 extern: 定义全局变量时,隐式包含 extern,但如果还加了 const 的话(被限制了本文件使用),就要显示加上 extern 了,否则别的文件无法引用。有一种情况例外:声明在头文件中的常量表达式(比如enum),包含它的每个文件都会自带一份(名称和值都一样),大部分编译器编译时便会将使用的地方替换成常量表达式,所以并不会为const变量开辟存储空间。(如果此时加了extern,反而会出现重复定义的报错
  • struct 和 class: 二者效果相同,唯一的区别在于默认访问级别不同。

趣味题

union Un
{
	int8_t a[4];
	int32_t b;
};
Un u;
u.b = 1;
if (u.a[0] == 1)
	cout << u.a[0] << endl; // 有输出,但是输出“笑脸”
// 思考:明明等于1,为什么输出不等于1?
// 猜测:== 是根据内存对比的,cout 却是根据内存类型(int)输出的。

第3章 标准库类型

  • vector: 下标操作不能用来新增元素;for 循环的判断条件用 != 而不是用 < ;界限用 .size() 不需要提前保存。
  • const_iterator: const ivec<int>::iterator iterconst int *p不一样,后者不可改变 *p 的值,但前者是不能 iter++。要想限定所指向的值不能改变,有专门的定义方式ivec<int>::const_iterator iter
  • size_type 和 difference_type: 一个 unsigned 一个 signed ,足够大来存储大小,应用于 string 、vector 当中。
  • 任何改变 vector 长度的操作都会使已存在的迭代器失效。
  • bitset 和 vector: 二者同为类模板,vector 抽离类型,bitset 抽离长度,在尖括号内给出长度值。
  • bitset 初始化: bitset 对象内存从0开始是低位到高位,但是输出时从高位到低位。string 对象和 bitset 对象之间是反向转化的(高低位颠倒)。

第4章 数组和指针

  • 概念: 类似于 vector 和迭代器的内置数据类型,尽量避免使用(容易出错难于调试),除非设计强调速度的良好程序。
  • 指针: 提供对其所指对象的间接访问,相对于迭代器,结构更通用一些。
  • 取地址操作符 &: 只有当变量用作左值时,才能取其地址。P.115
  • 指针注意事项: 最好在定义指针时就初始化,如果一定要分开,也要初始化为0。这样编译器才能检测出(未初始化、指向不可控地址,是无法检测出来的)。
  • void 指针:* 可以保存任何类型对象的地址。操作有限:1.与另一个指针进行比较;2.向函数传递 void* 指针或从函数返回 void* 指针;3.给另一个 void* 指针赋值。
  • 怎么理解? 如果指针指向一对象,可以在指针上加1从而获取指向相邻的下一个对象的指针。
  • 指针相减: 只要两个指针指向同一数组或有一个指向数组末端的下一单元,C++还支持对这两个指针做减法操作。得到的数据是标准库类型 ptrdiff_t,与 size_t 类型一样,是在 cstddef 头文件中定义的一种与机器相关的类型(可以和 vector中的 size_type、difference_type 做类比)。
  • 下标操作符 []: 使用下标访问数组时,实际上是对指向数组元素的指针做下标操作(没错,指针也可以用下标操作:p[i] 等效于 *(p+i),并且支持负数)。P.122
  • for 循环新知识: 只要定义的多个变量具有相同的类型,就可以在 for 循环的初始化语句中同时定义它们。(常规只能写一条)。
  • 指针是数组的迭代器: 循环遍历时,可以像迭代器一样,指针 p 等效于begin(),p + size 等效于end()。
  • 疑问: 为什么指向 const 对象的指针,在定义时不需要对它进行初始化?P.124
  • 解答: 和 const 指针作对比,与任何 const 量一样,const 指针也必须在定义时初始化。
  • 指针和 typedef: typedef string *pstring; const pstring cstr;如果把 typedef 当做文本扩展,就会错误的理解为 cstr是一种指针,指向 string 类型的 const 对象。正确答案是:cstr 是指向 string 类型对象的 const 指针。
  • cstring 的关系: cstring是 string.h 头文件的 C++ 版本,而 string.h 则是 C 语言提供的标准库。
  • 动态数组: int *pia = new int[10];1、这样创建的数组没有名字,只能通过地址间接访问堆中对象。2、类类型会调用默认构造函数,内置类型无初始化。可以跟一对圆括号统一初始化,但无法像数组变量一样,用初始化列表给元素提供各不相同的初值。
  • const 对象的动态数组: 必须在定义时就初始化,因为常量元素不允许被修改。正因如此,这样的数组实际上用处不大。
  • 允许动态分配空数组: 之所以要动态分配数组,往往是由于编译时并不知道数组的长度。size_t n = get_size(); int *p = new int[n]; for (int *q = p; q != p + n; ++q)有趣的是get_size()返回0的时候,也能正常运行,只是指针不能取引用。
  • 动态空间的释放: delete [] 表达式,如果遗漏了空方括号,会导致运行时少释放了内存空间,从而产生内存泄露。
  • 新旧代码兼容: C 风格的可以初始化 string 类型,但反之不行。必须采取以下方式,并且加上 const。还要注意中途修改了 str 后,该指针可能失效,所以最好是用之前拷贝一份。const char *pstr = str.c_str();

第5章 表达式

  • 除以和求模: 求模操作符号不同时,结果依赖于机器。但规律一定:如果求模的结果随分子的符号,则除出来的值向零一侧取整;如果求模与分母的符号匹配,则除出来的值像负无穷一侧取整。
  • 短路求值: 逻辑与和逻辑或操作符总是先计算其左操作数,当仅靠左操作数的值无法确定结果时,才会求解其右操作数。具有危险边界时适用。P.146
  • 位操作符: 对于符号位的处理依赖于机器,所以强烈建议使用 unsigned 整型操作数。
  • 位运算与 bitset: 标准库提供的 bitset 操作更直接、更容易阅读和书写、正确使用的可能性更高,并且对象的大小不受 unsigned 数的位数限制。
  • 移位操作符:中等优先级。
  • 赋值操作符: 赋值操作符的左操作数必须是非 const左值。数组名是不可修改的左值,因此不能作为赋值操作的目标。而下标和解引用操作符都返回左值,因此当作用于非 const 数组时,其结果可作为赋值操作的左操作数:int ia[10]; ia[0] = 0; *ia = 0;
  • 赋值操作符:低优先级、右结合性(与常规二元运算符不同)
  • 复合赋值操作符: 可以是以下十种:+= -= *= /= %= <<= >>= &= ^= |=。使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用作左操作数。
  • 自增和自减操作符: 前置操作返回对象本身,是左值。而后置操作返回的则是右值。对于 int 型对象和指针,编译器可优化掉这项额外工作,但对于更多的复杂迭代器类型,可能会花费更大的代价。因此,养成使用前置操作这个好习惯,就不必操心性能差异的问题。
  • 箭头操作符: 对于指向类类型的指针变量,访问其成员时可用p->foo;取代(*p).foo;
  • 条件操作符: 是C++中唯一的三元操作符,它允许将简单的 if-else 判断语句嵌入表达式中。
  • 逗号操作符: 逗号表达式是一组由逗号分隔的表达式,这些表达式从左向右计算。逗号表达式的记过是其右边表达式的值,如果是左值结果也是左值。
  • 优先级和结合性: 优先级决定操作数的结合方式,结合性决定操作数的计算顺序。P.161(但这二者都不能定义求值顺序P.163)
  • 动态创建对象 new: 定义变量时,必须指定其数据类型和名字。而动态创建对象时,只需指定其数据类型,而不必为对象命名。取而代之的是,new 表达式返回指向新创建对象的指针。
  • 动态创建对象的默认初始化:int *pi = new int;int *pi = new int();是不一样的,前者没有定义,后者初始值为0。
  • 撤销动态创建的对象 delete: 如果指针指向不是用 new 分配的内存地址,则在该指针上使用 delete 是不合法的。(编译器甚至无法发现错误。例如:int i; int *pi = &i; delete pi;在 delete 之后,重设指针的值
  • 零值指针的删除: C++保证:删除0值的指针时安全的。(虽然这样做没有任何意义。)
  • 动态内存的管理容易出错: 1、删除失败,该块内存无法返还给自由存储区,导致内存泄漏(不易发现,程序运行一段时间后内存不足才知道);2、读写已删除的对象。(删除后立即置0可避免);3、同一块内存空间连续两次delete。(自由存储区可能会被第二次删除破坏)
  • 隐式类型转换: 1、在混合类型的表达式中,操作数被转换为相同类型;2、用作条件的表达式被转换为 bool 类型;3、表达式初始化或者赋值某个变量,被转换为该变量的类型。
  • **算术转换:**研究大量例题是帮助理解算术转换的最好方法。P.170
  • 其他隐式转换: 1、**指针转换:**数组大多情况自动转换为指向第一个元素的指针;任意数据类型的指针都可转换为void*类型;整型数值常量0可转换为任意指针类型。
  • 命名的强制类型转换: dynamic_cast、const_cast、static_cast、reinterpret_cast。

趣味题:

if (val == true)
if (val)
// val 为bool类型时,二者等价。
// 如果不是,上面等于1满足,下面非0即满足。

第6章 语句

  • 复合语句: 用花括号括起来的块语句。
  • if 语句: 条件中可以是表达式,或者是一个初始化声明。必须初始化,然后转化为 bool 型的类型。类类型能否用在条件表达式中取决于类本身。IO通常可以,vector 和 string 一般不可用。
  • 悬垂 else 问题: 在 if 语句后加花括号是一个好习惯,可以避免这种二义性。P.185
  • switch 表达式: 求解的表达式可以非常复杂,并可以定义和初始化一个变量,但只能在结构内使用。
  • case 标号: 必须是整型常量表达式。
  • switch 内部的变量定义: 最好用花括号括起来行程块语句,不然下一个 case 能使用该变量,但有可能被该 case 标签跳过初始化。
  • while 和 do while: 后者的判断条件中不可以定义变量。
  • 使用预处理器进行调试:#ifndef NDEBUG #endif
  • 四种在调试时非常有用的常量: __FILE__文件名、__LINE__当前行号、__TIME__文件被编译的时间、__DATE__文件被编译的日期。
  • assert: 用来测试“不可能发生”的条件,只对程序的调试有帮助,但不能用来代替运行时的逻辑检查,也不能代替对程序可能产生的错误的检测(异常处理)。
    for 语句头定义的变量

第7章 函数

升华:函数的理解

  • **函数与操作符:**以前看操作符重载的时候,觉得操作符就是函数,实际二者的目的确实是一样的。
  • **调用操作符:**C++语言使用调用操作符()(一对圆括号)实现函数的调用。把()当做操作符,参数当做操作数。
  • **函数必须指定返回类型:**早期的C++版本可以不定义,会隐式返回 int 类型。
  • 引用形参的两个作用: 1、避免复制类类型或者大型数组,造成效率低下。2、可以返回想要的,额外的信息。
  • **const 引用:**如果函数唯一的目的是避免复制实参,则应将形参定义为 const 引用,避免让函数的使用遭到限制(const 实参无法传递)。更灵活的指向 const 的引用
  • **数组形参:**使用数组类型形参的函数,会被自动转化为指针。int*; int[]; int[10]三者完全相同。编译器只会检查实参是不是指针,类型是否匹配,不会检查数组的长度。P.221
  • **通过引用传递数组:**如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递本身,此时数组大小成为类型的一部分,编译器会检查是否匹配。int (&arr)[10]
  • **多维数组的传递:**与一维数组一样,编译器忽略第一维的长度。int matrix[][10]; int (*matrix)[10]这二者等效。
  • 传递给函数的数组的处理: 1、C风格字符串,末尾null字符作为结束标记;2、显示传递数组大小int j[] = {0, 1}; print(j, sizeof(j)/sizeof(*j));3、使用标准库规范,传递两个指针,一个指向第一个元素,一个指向最后一个元素的下一个位置。print(j, j+2);
  • **main 处理命令行选项:**第一个为参数个数(包含程序名),第二个有两种等效写法:char *argv[]; char **argv;
  • **return 语句:**void 函数不允许返回表达式,但是可以返回同为 void 的函数。隐式的 return 发生在函数的最后一个语句完成时。
  • **主函数 main 的返回值:**所有的非 void 函数都要有返回值,main 函数除外。它的返回值通常视为状态指示器,0代表成功,非0的意义因机器而不同。建议使用 cstdlib.h 中的宏定义 EXIT_SUCCESS、EXIT_FAILURE。
  • 可以返回引用(比如返回两个字符串中较长的那个),但千万不要返回局部对象的引用,返回局部对象的指针也一样不行。
  • **引用返回左值:**可以对返回值进行赋值。标准输出运算符<<返回的也是引用值。
  • 递归:递归函数必须定义一个终止条件,否则会一直调用自身知道程序栈耗尽。这种现象称为“无限递归错误”。满足了终止条件后,依次返回前面每个调用的返回值,这个过程称为此值向上回渗。主函数 main 不能调用自身。
  • **函数声明:**一个函数只能定义一次,但是可声明多次。
  • **函数原型:**函数原型描述了函数的接口,包括函数返回
  • 15
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值