C++ Primer 学习笔记 PartI C++基础

Ch1开始

这一章包含控制流,条件,循环,注释,标准IO等内容。略。

Part I C++基础

CH2 变量和基本类型

2.1 基本内置类型

2.1.1 算术类型

image-20240202162430838

2.1.1 类型转换
  • 向unsigned赋超出范围的值,结果取余,对于signed,结果未定义。
  • 不要混用signed和unsigned。表达式中既有signed又有unsigned,当signed为负值时会出现异常结果,因为它会自动转换为unsigned。
2.1.3 字面值常量

整型字面值:

  • 默认情况下,十进制为带符号数(int, long, long long),八进制和十六进制则都有可能(前三种加上uint,ull)。
  • 类型是能够容纳该数的有关类型中尺寸最小者,若超过所有类型的表示范围将发生错误。
  • short没有对应的字面值。
  • 十进制字面值中不含负号,负号的作用是取负值。

字符串字面值:

  • 实质是常量字符构成的数组

  • 编译器在末尾自动添加空字符'\0',因此实际长度比内容多1

  • 转义序列

    image-20240202164454063

  • 通过前后缀指定字面值类型

    image-20240204151111329其他字面值: true false nullptr

2.2 变量

2.2.1 变量定义

列表初始化,使用花括号。列表初始化如果存在丢失信息的风险,则编译器会报错。

默认初始化:函数体外为0,函数体内为未定义。

2.2.2 变量定义与声明的关系

声明:使得名字为程序所知

定义:创建与名字关联的实体

分离式编译:详见2.6.3和6.1.3

extern int i;   //声明
int j;			//声明并定义 
2.2.3 标识符
2.2.4 名字的作用域

2.3 复合类型

2.3.1 引用

引用即别名,本身不是一个对象。

定义时必须初始化,初始值不能是字面值,应该是对象,且引用类型与被引用对象的类型一致。

2.3.2 指针

指针本身是一个对象,也无须定义时初始化。

使用解引用符(*)来操作对象。

不能把int变量直接赋值给指针,即便值为0。

建议初始化所有指针。

void*指针

  • 特殊指针类型,可存放任意对象的地址
  • 只能与其他指针比较,作为函数的输入输出,或者赋值给另一个void*指针
  • 不能直接操作void*指向的对象

指向指针的引用

int i = 42;
int *p;
int *&r = p; //从右向左阅读,r表示指向指针的引用
r = &i;		//等价于p = &i
*r = 0;		//等价于*p = 0 

2.4 const 限定符

初始化和const

  • 默认状态下,const对象尽在当前文件内有效
  • 编译器在用到const变量处执行替换
  • 如要在多个文件之间共享const对象,必须在定义前加extern关键字
2.4.1 const的引用

只能用对常量的引用来引用常量(引用的类型与所引用对象保持一致)

引用的类型与所引用对象保持一致,但存在例外情况:初始化常量引用时允许用任意表达式作为初始值,如字面值和非常量,只要该表达式能够转换成引用的类型即可。

int i = 42;
const int &r1 = i; 	//ok
const int &r2 = 42; //ok
const int &r3 = r1 * 2; //ok
int &r4 = r1 * 2; //wrong,非常量引用不能这么做

实质上,此时编译器创建了一个对应类型的临时量,该临时量被常量引用绑定。

常量引用仅对引用可参与的操作进行了限定,它可以引用非常量,但不能通过引用修改非常量的值。

2.4.2 指针和const

指针可以指向常量或非常量,指向常量的指针不能用于修改所指对象的值。

指针类型必须与其所指对象类型一直,但存在例外情况:允许一个指向常量的指针,指向一个非常量对象,与2.4.1的情况相似。总得来说,可以认为const类型的指针和引用自认为指向了常量,自觉地不去改变所指对象的值。

const指针

由于指针本身是一个对象,允许把指针本身定为常量。

int errNumb = 0;
int *const curErr = &errNumb; // 指向非常量的常量指针。可以通过curErr修改errNumb
const double pi = 3.14;
const double *const pip = π //指向常量的常量指针。
//从右向左阅读,表示pip是常量对象,且为指针。该指针指向的对象为const double。
2.4.3 顶层const

顶层const表示指针本身是常量(更一般的,表示任意对象本身是常量),底层const表示所指对象是常量(与指针、引用等复合类型的基本类型有关)。

当执行对象的拷贝操作时,顶层const通常不受影响,而底层const却不能忽视要求拷入和拷出对象具有相同的底层const资格,或者数据类型能够转换。一般来说,非常量可以转换成常量,反之则不行。

2.4.4 constexpr 和 常量表达式

常量表达式

指在编译期间就得到计算结果的表达式。

const int N = 114514; // yes
const int M = N + 1;  // yes
int P = 998244353; 	  // no,因为类型是非常量
const int sz = size(); //no,因为编译期无法获取

constexpr

C++ 11 规定,允许将变量声明为constexpr类型以由编译器验证变量的值是否为常量表达式。

constexpr const int sz = size(); //只有当size()是constexpr函数时才正确

6.5.2节将深入介绍constexpr函数。

如果你认定一个表达式是常量表达式,就声明为constexpr。

字面值类型

容易得到值的类型。如算术类型,引用,指针。

非字面值类型:自定义类,IO库,string类型等。

指针和引用能够定义为constexpr,但初始值受严格限制。

指针和constexpr

constexpr把它所定义的对象置为了顶层const,与指针所指的对象无关

2.5 处理类型

2.5.1 类型别名

传统方法 typedef

typedef double wages;
typedef wages base, *p; //base是double的别名,p是*double的别名

新标准 别名声明

using SI = Sales_item;
2.5.2 auto类型说明符

因为一条声明语句中只能有一个基本数据类型,因此该语句中所有变量初始基本数据类型必须一样。

auto i = 0, *p = &i; 	//ok,相当于int i = 0, *p = &i;
auto sz = 0, pi = 3.14;	//wrong

auto一般会忽略顶层const,而底层const被保留

const int ci = i, &cr = ci;
auto b = ci;	//b为int
auto c = cr;	//c为int
auto d = &i;	//d为int*
auto e = &ci;	//e为(const int)*

如果希望推断出的auto类型是顶层const,需要明确指出

const auto f = ci; //ci的推演类型为int,f为const int

还可以将引用类型设置为auto,这时原来的初始化规则仍适用

auto &g = ci;	//ok
auto &h = 42;	//wrong, 非常量引用不能绑定字面值
const auto &j = 42; //ok,常量引用可以绑定字面值
2.5.3 decltype类型指示符

对于需要根据表达式推断类型,而不用该表达式初始化变量的情况,C++11引入了第二种类型说明符decltype

decltype(f()) sum = x; //sum的类型为f的返回类型

const int ci = 0, &cj = ci;
decltype(ci) x = 0; // const int x = 0;
decltype(cj) y = x; // const int &y = x;
decltype(cj) y; //错误,引用必须初始化

decltype与引用

有些时候表达式将向decltype返回引用。这种表达式通常可以作为一条复制语句的左值。

int i = 42, *p = &i, &r = i;
decltype(r + 0) b; //b是未初始化的int,因为r+0的结果是int型值
decltype(*p) c; //错误,c是int&,必须初始化

如*p, 表达式的内容是解引用操作,decltype的结果为int&。

与auto不同,decltype的结果形式与表达式形式密切相关,例如,当表达式被括号括起时,decltype的结果将为引用。

decltype((i)) d; //错误,d是int&,必须初始化
decltype(i) e;	 //正确,e是未初始化的int。

decltype((表达式))的结果永远为引用。当且仅当表达式结果为引用时,decltype(表达式)的结果为引用。

2.6 自定义数据结构

略。

预处理器概述

预处理器看到#include 标记时,用指定头文件的内容代替#include

此外,还有头文件保护符:

'#define' 把一个名字设置为预处理变量

'#ifdef' 当且仅当变量已定义时为真

'#ifndef' 当且仅当变量为定义时为真

一旦检查结果为真,则执行后续操作,直至遇到#endif为止。

使用这些功能能够有效防止重复包含的情况发生,因此最好习惯性地设置保护符,不管该文件是否被其他文件包含。

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
}
#endif

预处理变量无视C++语言中关于作用域的规则。

CH3 字符串,向量和数组

因为这部分内容在算法竞赛中较为常见,仅记录不太熟悉的内容以及编码提示等。

3.1 using

头文件不应该包含using声明,因为所有引用它的文件都会受此影响,可能产生冲突。

3.2 string

由于历史原因,字符串字面值不是string类对象,使用string类加法时需确保加号两侧至少有一个是string对象。

3.3 vector

vector是一种类模板。

range for语句体内不应改变其遍历序列的大小。

3.4 迭代器

*iter 返回iter的引用

iter->num 等价于(*iter).num

++ / -- iter 令iter指示下一个/上一个元素

iter1 ==/!= iter2 判断是否指向同一个元素,或指向同一个容器的尾后迭代器

迭代器类型

iterator和const_iterator。如vector<int>::const_iterator

常量迭代器类似常量指针,只能读不能写。如果对象类型为常量,只能使用常量迭代器,否则都可以。

凡是使用迭代器的循环体,都不应向迭代器所属容器中添加元素。

迭代器运算

与整数加减,仍得到迭代器。

迭代器加减,得到距离。类型是名为difference_type的带符号整数。

迭代器比较,前者小于后者。

使用迭代器进行二分查找

auto beg = v.begin(), end = v.end();
auto mid = beg + (end - beg) / 2;
while (mid != end && *mid != target) {
    if (target < *mid) end = mid;
    else beg = mid + 1;
    mid = beg + (end - beg) / 2;
}

3.5 数组

int *(&arry)[10] = ptrs;
//从内向外阅读:arry是一个数组的引用,该数组含有10个int型指针

该部分内容基本与C语言相同

CH4 表达式

待填

CH5 语句

待填

upd:
弃坑,并提供一条迅速掌握c++通过考试并可以写点东西的学习路径:
1.学习https://www.bilibili.com/video/BV1ob411q7vb 并完成http://cxsjsxmooc.openjudge.cn/2023t3spring/ 习题
2.学习https://www.bilibili.com/video/BV1N34y1H7x7

posted @ 2024-02-04 22:12  _vv123  阅读(17)  评论(0编辑  收藏  举报