c++primer_1:第一、二章

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 = &num;
//不可以更换指向的对象
//可以通过*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((表达式)):加一层或多层括号看作表达式
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值