c++基础,准备快速过一遍
第一章:开始
1.1编写一个简单的C++程序
函数定义包括哪四个部分?
return类型和函数返回类型“相容”是什么意思?#
数据、变量、类型的关系是什么?
变量是数据的载体
一个变量具有类型T,则它的内容和可以进行的计算都被T所规定,不可越界
iostream库中的stream如何理解?
iostream库中的四个对象,注意不是类
对象名 | 功能 |
---|---|
cin | 标准输入 |
cout | 标准输出 |
cerr | 标准错误 |
clog | 一般信息 |
#include <iostream>
这里的iostream是头文件还是源文件?
"<<"是一个什么运算符?左右两侧的“类型”要求是什么?运算返回结果是什么?
左侧必须是ostream对象,右侧必须是一个确定的值,运算结果还是原来的ostream对象,形成一条链
">>"是什么运算符?左右“类型”要求是什么?
如何理解“字符串字面值常量”?#
endl作为一个操纵符,它操纵了什么?
1、结束当前行
2、刷新缓冲区
如何理解刷新缓冲区?
缓冲区位于内存,程序的输出先放在缓冲区中,然后进入输出流,最后被设备所使用(比如被显示屏显示),而刷新缓冲区就是将缓冲区是的数据都写入输出流。
std是一个命名空间,作用是什么?
作用域运算符“::”的作用是什么?
单行注释结束符是什么?
多行注释可嵌套吗?为什么?
多行注释应该采取什么风格,让多行注释更加显眼?
在什么情况下可能会发生对多行注释的嵌套?如何进行?
用单行注释符注释多行注释的每一行,这样不会引起任何问题。
控制流的部分跳过。
类的简单介绍跳过。
第二章:变量和基本类型
2.1 基本内置类型
基本数据类型包括哪些?算术类型+空类型(void)
算术类型包括哪些?字符、整型数、布尔值、浮点数。
空类型有哪些使用场景?
当函数不返回任何值的时候,用空类型作为返回类型。
2.1.1 算术类型
算术类型的大小是固定的吗?
不是,C++标准仅规定每种算数类型的最小尺寸值,允许编译器赋予它们更大的尺寸。
四种char的区别?
char : 基本字符集(最小8位)
wchar_t : 扩展字符集(最小16位)
char16_t : Unicode字符集(16位)
char32_t : Unicode字符集(32位)
整型的区别?
short : 16
int : 16
long : 32
long long : 64
阅读34页“内置类型的机器实现”,回答问题:
各种内置类型在机器层面的形式是什么?0和1
既然不同内置类型在机器层面的形式相同,那么如何进行区分?
我们仅仅知道一个数据在内存中的起始位置是不够的。
还需要知道该数据的尺寸,以及对这一串0和1如何“翻译”。
类型决定了数据所占的比特数以及如何解释这些比特的内容。
浮点型:(一般情况下)
fload : 一个字(转换为十进制约有7个有效位)
double : 两个字(转换为十进制约有16个有效位)
long double : 三或四个字
signed和unsigned可以加在int \ short \ long \ long long 之前,表示有符号、无符号,如果不加,默认有符号
signed和unsigned可以加载char之前,表示有符号、无符号,如果不加,并不是默认有符号
char是有符号还是无符号,取决于编译器类型。
C++标准规定:signed类型的正值和负值应该平衡。比如signed char应该是-127到127,但是大多数情况下会将 1000 0000 认为是-128,于是涵盖了-128到127的范围
数据类型选择的建议!
1、在满足条件的情况下,尽量选择无符号类型。
2、在int范围不够的情况下,尽量选择long long。
3、不要用char进行数值计算。
4、对于较小的整数,最好明确指定有无符号。
5、浮点运算尽量使用double。
说明:
1、无符号类型范围较大。
2、long类型和int类型常常有一样的尺寸。
3、char类型有时是有符号的,有时又是无符号的。
4、float通常精度不够,在运算上也不比double有很大的优势。
2.1.2 类型转换
如果无符号数和有符号数混合计算会发生什么?
有符号数被强制转换为无符号数,如果有符号数是一个较小的负数,转换后就变成了一个很大的正数,从而造成计算上的错误。
无符号数和无符号数计算应该注意什么?
确保计算结果>=0
此外,无符号数++,无符号数–也应当注意在>=0的范围内进行计算。
整型字面值有无类型?是什么类型?
int \ long \ unsigned long \ long long \ unsigned long long 中的最小者
字符串对应什么数据结构?为什么实际长度比内容多1?
空字符 ‘\0’
字符串分行书写的方式:
"first line "
“and second line”
常见的转义字符有:
换行\n 横向制表\t 报警\a 退格\b 双引号" 单引号’ 反斜线\ 问号? 回车\r
字面值的内容跳过
2.2 变量
2.2.1 变量定义
定义时可以不进行初始化,但是初始化必须在定义时进行。
初始化和赋值的区别是什么?
初始化重在“初始”,也就是说对象在创建之初就获得了一个特定的值,而赋值是在使用过程中,使用新的值来覆盖旧的值。
什么是默认初始化?
如果变量定义的时候没有人为显式地指定一个初始化值,那么将进行默认初始化过程。
默认初始化将变量初始化为0。
默认初始化在什么情况下不会发生?会造成什么影响?
当内置类型变量被定义在函数体内部则不会发生。
如果在函数体内定义的变量没有显式地初始化,由于不会发生默认初始化,所以这时该变量未初始化。
这不仅会造成程序运行错误,而且往往编译器无法定位未初始化的变量,难以调试。
介绍一种特殊的初始化形式:列表初始化,它使用花括号来进行初始化。
int num{10};
它等价于:
int num = 10;
2.2.2 变量声明和定义的关系
分离式编译,如何理解?
程序可以分为多个文件独立编译。
分离式编译,如何实现?为什么?
声明和定义分开。
设想多个文件需要使用同一个变量,那么如果都include,那么相当于多个文件都“包含”了这个变量。由于变量只能定义一次,但是可以声明多次,所以我们只需要让多个文件包含这个变量的声明,而让一个专门的文件包含这个变量的定义。
声明和定义的区别?
声明:规定变量类型和名字
定义:在声明的基础上申请存储空间,(也可能会)进一步初始化。
声明:extern int i;
定义:int i;
注意:extern int i = 1; 等价于 int i = 1;
extern int i = 1; 这种语句不要使用,而且在函数体内部使用会造成错误。
变量只能被定义一次,但是可以被多次声明
静态类型,在编译阶段检查类型。编译器在对象处在错误的运算语句中时能够及时发现,前提是让编译器事先知道该对象的类型。这也是先声明后使用的原因。
2.2.3 标识符
大小写、不与语言自带标识符冲突、不能连续两个下划线
下划线不能紧连大写字母
命名规范:
- 见名知义
- 变量名消息字母
- 类名大写字母开头
- 多个单词之间应当区分,比如加下划线或者采用驼峰命名
2.2.4 作用域
作用域:变量有效的区域,大多以花括号分割。
为什么说“大多”?因为有一个“全局作用域”,对于定义于所有花括号之外的名字,采用全局作用域。
花括号包围起来的作用域称之为:块作用域。
作用域可以嵌套,内外层出现同名变量时(我们应当避免这种情况发生),优先访问内层变量,内层如果想要访问全局变量,可以使用作用域操作符双冒号::来访问,::左侧为空说明想要访问全局变量。
2.3 复合类型
2.3.1 引用
引用相当于给对象起一个别名。
引用必须初始化,绑定对象(引用初始化)是一个引用的使命。诞生之初就要明确,且绑定的对象不得更改。
注意:
- 不能定义引用的引用(引用本身不是对象,只是一个名字)
- 引用不能绑定字面值(只能绑定对象)
2.3.2 指针
指针和引用对比
引用 | 指针 |
---|---|
不是对象,只是一个名字 | 是一个对象 |
必须初始化 | 可以不初始化 |
不可更改引用的对象 | 可以更改指向的对象 |
判断一个变量是不是一个对象,最直接的方式就是看它在内存中有没有对应的存储地址,如果没有,它就不是一个对象
对于引用来说,它不是对象,没有地址,无法通过&符号来取地址
“&”
语句:
int age = 20;
int &refAge = age;
int *pAge = &age ;
int ageValue = *pAge ;
这里两个&的含义不同:第一个是引用符号,第二个是取地址符。
两个*的含义不同,第一个是指针符号,第二个是解引用符。
int *pRefAge = &refAge ; 这样的语句是错误的,因为引用不是对象无法取地址。
char *pAge = &age ; 这样的语句是错误的,因为类型不匹配。
指针4种状态
- 指向一个对象
- 可以通过指针来间接访问对象
- 指向对象的下一个位置
- 空指针
- 这两种情况虽然没有指向任何对象,但是是有效指针。
- 无效指针
- 可能会在编译器不觉察的情况下,造成严重后果,因为它在错误的使用下会修改我们本不希望修改的内存数据。
- 往往是程序崩溃的原因。又往往难以定位。
- 一种情况是:一个指针没有被初始化,那它就是无效指针。注意:空指针也需要初始化。虽然没有被初始化,但是很有可能程序在运行时发现这个指针指向一个对象!这会造成严重的后果。
- 所以任何一个指针虽然不明确要求初始化,但是在编程时一定要及时赋值,实在不行就初始化为nullptr,避免无效指针引起程序崩溃。
空指针初始化方法:
最好方法:使用字面值nullptr : int *pa = nullptr ;
较差方法:使用预处理变量NULL或者直接使用0:int *pa = 0;
不同点:nullptr和1,2,3,4,5一样是字面值,而NULL是一个变量,只是值为0.
指针的特殊使用
用在条件句中:if(pa) 如果pa是空指针,则返回false,如果指向一个对象则返回true
用在比较句中:if(pa==pb),返回true的条件
- pa/pb存放的地址相同
- 指向同一个对象
- 指向同一个对象的下一个地址
- 都为空
2.3.3 较复杂的情况
int a,*pA,&refA ;
//一句话声明了三个不同类型的变量:int、指针、引用
int a = 1;
int *pA = &a;
int *&p = pA;
//理解即可,最最好别这么写
2.4 const限定符
const对象必须初始化。
int i = 1;
const int j = i;
//这样做是可以的,含义是将当前i的值拷贝给const变量j
- 编译器在编译的时候,将const类型的变量替换为对应的值。
- 于是我们发现以下两个需求是冲突的:
- 由于变量不允许重复定义,在一个文件中定义一个const类型的变量后,在其他文件中只能声明这个const类型的变量
- 每一个文件必须能够访问到const变量对应的值,否则无法编译
- 为了解决这个冲突,c++中默认设置为:const变量仅在本文件中有效。这样即使在多个文件中定义同一个变量也不会冲突。
- 如果确实需要共享
2.4.1 const的引用
对const的引用,在定义时也需要声明为const:
const int num = 1;
const int &refNum = num; //对
int &refNum = num; //错
//引用const类型,则必须以这样的格式定义:const int &变量名 = 引用对象。这并不是说这个引用本身是个常量,而是说它指向const类型就应该这么写。这里const这个单词是修饰它指向的数据类型的,不是修饰它本身的。
int &refNum = 10; //错,引用的初始值必须是一个对象
const int num2 = 2;
refNum = num2; //错,引用不能更改
int num3 = 3;
const int &refNum3 = num3; //对,可以让refNum3自以为引用了一个常量
int &ref2Num3 = num3;
refNum3 = 10; //错,虽然num3本身不是const类型的,但是refNum3以为它是const类型的,所以不能通过refNum3来修改Num3
ref2Num3 = 10; //对,它不是const引用。
对const的引用不是常量:
是“常”:因为引用的对象不能改变
不是“量”:因为引用只是一个名字不是一个对象。
double dval = 1.1;
const int &ri = dval;
等价于:
double dval = 1.1;
int temp = dval; //temp = 1
const int &ri = temp;
//这是合法的,其中temp成为临时量。这里的temp只是帮助我们理解的,实际上它没有名字。但是不建议这样写,因为没有意义。
而对于不是const的情况:
int &refDval = dval;
这是非法的,由于临时量的存在,我们无法通过refDval来修改dval,这就使得这一引用毫无意义。
//如果绑定一个字面量:
const int &refInt = 10;
则也是创建了一个临时量temp=10供refInt来绑定。那么以下写法也是错误的,因为毫无意义
int &refInt2 = 10;
2.4.2 const和指针
一个指针可以认为它指向了一个const类型的变量,不管它是不是真的
最易混淆的是两种指针:指向常量的指针和常量指针
指向常量的指针
const double dval = 3.14;
const double *pDval = &dval;
//将变量的地址赋予指针
//第二行中的const并不是修饰pDval本身的,而是说pDval的指向:指向一个常量
//任何一个指向常量的指针,都应该使用第二行这样的格式
//可以更换指向的对象,只要待更换的对象也是常量
const double dval2 = 11.1;
pDval = &dval2;
//不可以改变对象的值
*pDval = 22.2; //这是错误的
常量指针
int num = 1;
int *const pNum = #
//不可以更换指向的对象
//可以通过*pNum = 2;这样的方式来改变对象的值。
为什么在引用中没有这两种说法呢?因为引用本身从来都是const的,也就是说,一个引用指向的对象从引用定义开始就伴随这个引用终生,不能更改。所以引用本身天然就是const类型的。
注意:这里说的const是指引用本身,而不是引用的对象。
2.4.3 顶层底层const、指针之间的赋值
指向常量的常量指针:
const int *const pToConstNum = &num2;
这里第一个const是底层const,修饰所指对象,第二个是顶层const,修饰指针本身
我们来看一个声明语句:
const int *const pToConstNum = &num2;
const int const 这一部分是在说明变量的类型
以号为分隔,前半部分是“基本数据类型”部分,后半部分是“声明符”部分
我们所说的底层const实际上就是在"基本数据类型"部分,顶层const实际上就是在“声明符”部分。
在后文typedef中也会提到此概念
(const int *) = (int *) :正确
(int *) = (const int *):错误
上面的两个const都是底层const,这体现了底层const对指针之间赋值操作的限制。
2.4.4 constexpr、常量表达式、字面值类型
什么是常量表达式?
不会随着运行而改变、在编译过程就能得到计算结果的表达式
常量表达式最简单的举例:字面量
const int a = 1; //是
const int b = a+1; //是
int c = 1; //不是
const int d = getNum(); //不是
getNum()会随着运行而改变,在编译期间不能得到计算结果。
constexpr
constexpr int e = getNum();
//只有当getNum()不随着运行而改变、在编译期就能得到计算结果时,这条语句才是正确的。
//当满足这个条件时,尽量将语句添加constexpr声明而不是用const,这样可以减少程序错误,增加可读性,便于优化。
字面值类型
值很简单的类型。
比如:int / long / short / double / bool 这些算术类型
比如:int &refNum 这种引用类型
比如:int *pNum 这种指针类型
值不简单的类型不是字面值类型:
比如:类(对象)、string类型
在c++中,有这样一个特点:对于定义于函数体之外的对象,或者虽然定义于函数体之内,但是有效范围超出函数的对象或者变量,在内存中拥有固定地址。
而在函数体中定义的一般对象,并非存在于固定地址中。
constexpr指针它的初始值必须是以下三种情况:
- nullptr
- 0
- 固定地址中的对象
constexpr指针声明方式:虽然声明在底层const的位置,但是作用相当于一个顶层const,不许修改所指的对象:
constexpr int *q = nullptr;
2.5 处理类型
2.5.1 类型别名
typedef int size; //基本类型
typedef size *pSize; //复合类型
using count = size; //新的写法
难点在于处理复合类型比如指针的情况:
比如:复合再复合:
typedef int *pInt;
pInt *ppInt;
//这里的ppInt是一个指向指针的指针(二维)
再比如:复合加const
typedef char *pString;
const pString cpStr;
//这里的cpStr是一个常量指针,而不是指向常量的指针
2.5.2 auto类型说明符
auto的作用:
让编译器通过初始值来推算变量的类型。
int a = 1, b = 2, &d = a;
auto c = a + b;
//则推算c的类型为int
auto c = a + b, c2 = 1.1;
//错误,因为c是整型而c2是浮点型,不一致。
//这是一条声明语句,一条声明语句只能有一个基本数据类型
//auto特点:忽略顶层const,推算底层const
auto left( int ) = right( int );
auto left( int ) = right( const int ); //忽略顶层const
const auto left( int ) = right( int ); //显式说明顶层const
auto left( int 星 ) = &right( int ); //推断指针的方式
auto left( const int 星 ) = &right( const int ); //指针的底层const
const auto left( int 星 ) = right( int 星 ); //指针的顶层const
const auto left( int 星 ) = &right( int ); //指针的顶层const
auto left( int ) = right( int & ); //引用符号&被忽略,看作一个赋值语句,想使用引用只能用下面的方法
auto &refRight ( int & ) = right( int ); //绑定
const auto &refRight ( const int & ) = right( int / const int / 字面值 ); //引用的底层const
2.5.3 decltype 类型指示符
decltype() 不会对顶层const进行特殊处理,也不会消除引用
decltype() 可以传入表达式,包括函数:func(),注意写上括号
特殊的表达式包括:
decltype(整型引用) 整型引用
decltype(整型引用 + 整型) 整型
decltype(整型解引用) 整型引用
decltype((整型)) 整型引用
decltype(值):不加括号看作值
decltype((表达式)):加一层或多层括号看作表达式