第一部分
1、一个函数的定义包含4部分:返回类型、函数名、参数列表和函数体。main函数的返回类型必须是int。函数体是以{}包含的语句块。
return 0;该语句在main函数中可加可不加,因为编译器会默认加上这一句,建议是加上的。
2、如何编译程序是依赖于使用的操作系统和编译器。IDE(集成开发环境):将编译器和其他程序创建和分析工具包装在一起。编译器的一部分工作是寻找程序文本中的错误,包括语法错误、类型错误、声明错误。修改完一个错误就开始编译而不是将所有错误都修改后才开始编译,这就是编辑-编译-调试周期。
3、C++没有定义任何输入输出(IO)语句,是使用一个包含全面的标准库来提供IO机制的:iostream库。
这里需要注意一点:缓存刷新操作可以保证到目前为止程序所产生的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入流。若程序崩溃,输出可能还留在缓冲区中,从而导致关于程序崩溃位置的错误推断。
4、C++的注释
单行注释和界定符对注释。注释界定符可以放置在任何允许放置制表符、空格符或换行符的位置。当助释放跨越多行时,最好显式之处其内部的程序行
都属于多行注释的一部分,可以在注释内的每行都以一个*号开头。注释界定符不能嵌套,否则会出现意料之外的错误。
5、while和for支持反复执行一段代码,直至条件不成立为止。
while的语法是:
while(condition)
statement
while的执行过程是交替检测condition条件和执行关联的语句statement,知道condition为假时停止。
for语句包含循环头和循环体。循环头控制循环体的执行次数,有初始化语句、循环条件以及表达式。for的语法:
for(init-statement; condition; expression)
{
statement
}
总体执行流程:
①循环入口处执行变量初始化语句;
②检测变量是否满足循环条件。满足则执行循环体,否则退出循环;
③执行循环表达式;
④重复②-③的步骤,直至不满足条件
对于希望读取数量不定的输入数据的情况,可以使用while和流的方式实现。在Windows系统中,输入文件结束符的方法是Ctrl+Z,再按Enter或Return键。Unix系统则是Ctrl+D。
6、C++程序很大程度上是格式自由的,不存在唯一正确的风格,但保持一致性是最好的。在选择格式风格时,考虑一下会对程序的可读性和易理解性有何影响。
7、类实际上可以看做一个新的类型。类决定了类类型对象上可以使用的操作。
8、文件重定向机制允许我们将标准输入和标准输出与命名文件关联起来,如:
app.exe < infile > outfile
其中**<是输入重定向,>是输出重定向**。用>时,如果存在目标文件,则会清空原先内容,没有目标文件则新建。要是想追加内容,用>>。
相关术语
术语 | 说明 |
---|---|
程序块 | 零条或多条语句的序列 |
缓冲区 | 一个存储区域,用于保存数据。默认情况下,cin会刷新cout,程序非正常终止时也会刷新cout。cerr的数据是不缓冲的,clog的数据是缓冲的 |
类 | 一种用于定义自己的数据结构及其相关操作的机制 |
数据结构 | 数据及其上所允许的操作的一种逻辑组合 |
编辑-编译-调试 | 使程序能正确执行的开发过程 |
命名空间 | 将库定义的名字放在一个单一位置的机制,用于避免命名冲突 |
标准库 | 一个类型和函数的集合,每个C++编译器都必须支持。 |
std | 标准库所使用的命名空间 |
字符串常量 | 零或多个字符组成的序列 |
未初始化的变量 | 未赋予初值的变量 |
9、任何常用的编程语言都具备的公共语法特征:
①整型、字符型等内置类型
②变量
③表达式和语句
④函数
大多数编程语言通过2种方式进一步补充其基本特征:①赋予程序员自定义数据类型的权利,实现对语言的扩展;②将一些有用的功能封装成库函数提供给程序员。
10、C++是一种静态数据类型语言,类型检查在编译时,因此编译器必须知道程序中每一个变量对应的数据类型。
11、C++的基本内置类型
C++提供了一组内置数据类型、相应的运算符以及为数不多的几种程序流控制语句,这些元素共同构成了C++语言的基本形态。C++强大的能力显示于它对程序员自定义数据结构的支持。
12、基本内置类型包括:算术类型和空类型在内的基本数据类型,算术类型包含字符、整型数、布尔值和浮点数。算术类型的所占字节数与不同机器有关。
13、内置类型的机器实现
计算机是以比特序列存储数据,非0即1。大多数计算机以2的整数次幂个比特作为块来处理内存,可寻址的最小内存块称为“字节”,存储的基本单元称为“字”,通常由几个字节组成。
14、如何选择合适的类型
①当明确数值不可能是负数时,选用无符号类型;
②使用int执行证书运算,若超过int的表示范围,选用long long;
③在算术表达式中不要使用char或bool,类型char在不同的机器上表现可能不同,最好明确其是unsigned还是signed;
④执行浮点数运算选用double,因为float通常精度不够而且双精度浮点数和单精度浮点数的计算代价相差无几。long double提供的精度在一版情况下是没有必要的,而且运行时消耗也不容忽视。
15、类型转换
表达式中最好不要同时存在带符号数和无符号数,当带符号数类型取值为负时会出现异常的结果,带符号数会自动转换成无符号数;
当从无符号数中减去一个值时,不管这个值是不是无符号数,都必须确保结果不是负值。
16、字面值常量
字面值常量的形式和值决定了它的数据类型。默认情况下,十进制字面值是带符号数,八进制和十六进制字面值可能是带符号数也可能是无符号的,整型字面值的类型是int、long和long long中尺寸最小的那个,八进制和十六进制的类型是int、unsigned int、long、 unsigned long 、long long和unsigned long long,前提是该类型能容纳下当前的值,若一个字面值和它关联的最大的数据类型都放不下,将产生错误。
17、转义序列
存在2类字符程序员是不能直接使用的:不可打印的字符(退格或其他控制字符,因为不可视)以及在C++中有特殊含义的字符(单引号等),这些情况下就需要使用转义序列,均以反斜线作为开始,C++规定的转义序列包括:
字符 | 转义序列 | 字符 | 转义序列 |
---|---|---|---|
换行符 | \n | 横向制表符 | \t |
报警(响铃)符 | \a | 纵向制表符 | \v |
退格符 | \b | 双引号 | " |
反斜线 | \ | 问号 | ? |
单引号 | ’ | 回车符 | \r |
进纸符 | \f |
18、变量
提供一个具名的、可供程序操作的存储空间。变量有具有数据类型,决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围,以及变量能参与的运算。对C++ 程序员而言,“变量”和“对象”一般可以互换使用。
变量定义的基本形式是:
类型说明符 变量名列表;
示例:
int nCount,nSum,nLoop;
int nCount = 0, nSum = 2, nLoop;
std::string strBook = "test";
如上,变量名以逗号分隔,最后以分号结束。列表中每个变量名的类型都由类型说明符指定,定义时还可以为一个或多个变量赋初值。
19、对象
对象是指一块能存储数据并具有某种类型的内存空间。
对象在创建时获得了一个特定的值,即对象被初始化了。在C++中,初始化和赋值是2个完全不同的操作。
20、初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。
当用于内置类型的变量时,使用列表初始化且初始值存在丢失信息的风险,编译器将会报错。如:
long double ldCount = 3.1415926536;
int nCount{ldCount}; //存在丢失信息的风险,不会执行转换
int nSum(ldCount); //执行转换,丢失部分值
21、默认初始化
①在定义变量时没有指定初值,则变量被默认初始化,即被赋予了“默认值”。默认值是什么由变量类型决定,同时定义变量的位置也会对此有影响。
②若是内置类型的变量未被显示初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0。但是存在一种例外的情况:定义在函数体内部的内置类型变量将不被初始化。一个未被初始化的内置类型变量的值是未定义的,若试图拷贝或以其他形式访问此类值将会引发错误。编译器没有被要求检查此类错误。
如下:
int nLoop; //初始化为0
int _tmain(int argc, _TCHAR* argv[])
{
int nLoop; //未初始化,值不确定
for (; nLoop < 10; ++nLoop)
{
int nValue = ChangeInt(nLoop);
std::cout << "print " << nValue << std::endl;
}
system("pause");
return 0;
}
③每个类各自决定其初始化对象的方式,是否允许不经初始化就定义对象也由类自己决定,若类允许这种行为,类将决定对象的初始值到底是什么。
④类的对象若没有显式地初始化,则其值由类确定。
22、分离式编译
该种机制允许将程序分隔为若干个文件,每个文件可被独立编译。因此需要将声明和定义区分开。
变量声明规定了变量的类型和名字,定义除了规定变量的类型和名字之外,还申请存储空间,可能会为变量赋初值。
extern int i; //声明i
int i; //声明并定义i
extern int i = 12; //定义i,初值12
由上可知,若只想声明一个变量,就在变量名前添加关键字extern,且不要显示初始化变量,因为任何包含显式初始化的声明都是定义。
在函数体内部,若试图初始化一个由extern关键字标记的变量会引发错误:
error LNK2001: unresolved external symbol "int i" (?i@@3HA)
注意:变量能且只能被定义一次,但是可以被多次声明。
23、静态类型
主要是指在编译阶段检查类型。其中检查类型的过程称为类型检查。
24、标识符
C++的标识符由字母、数字和下划线组成,其中必须以字母或者下划线开头。标识符的长度没有限制,但是对大小写敏感,如:
int nCount, ncount, NCOUNT; //定义了3个不同的int变量
用户自定义的标识符中不能连续出现2个下划线,也不能以下划线紧连大写字母开头,而且定义在函数体外的标识符不能以下划线开头。
25、变量命名规范
①标识符要能体现实际含义
②变量名一般用小写字母
③用户自定义到的类型一般以大写字母开头
④若标识符由多个单词组成,则单词间应有明显区分
26、C++的关键字
关键字 | 含义 | 关键字 | 含义 |
---|---|---|---|
alignas | continue | ||
friend | register | ||
true | alignof | ||
decltype | goto | ||
reinterpret_cast | try | ||
asm | default | ||
if | return | ||
typedef | auto | ||
delete | inline | ||
short | typeid | ||
bool | do | ||
int | signed | ||
typename | break | ||
double | long | ||
sizeof | union | ||
case | dynamic_cast | ||
mutable | static | ||
unsigned | catch | ||
else | namespace | ||
static_assert | using | ||
char | enum | ||
new | static_cast | ||
virtual | char16_t | ||
explicit | noexcept | ||
struct | void | ||
char32_t | export | ||
nullptr | switch | ||
volatile | class | ||
extern | operator | ||
template | wchar_t | ||
const | false | ||
private | this | ||
while | constexpr | ||
float | protected | ||
thread_local | const_cast | ||
for | public | ||
throw |
27、C++操作符替代名
操作符 | 含义 | 操作符 | 含义 |
---|---|---|---|
and | bitand | ||
compl | not_eq | ||
or_eq | xor_eq | ||
and_eq | bitor | ||
not | or | ||
xor |
28、名字的作用域
同一个名字若出现在程序的不同位置,可能指向的是不同的实体。同一个名字在不同的作用域中可能指向不同的实体。名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。
建议:在对象第一次被使用的位置附近定义,有利于赋合理的初值。
②作用域能够彼此嵌套。被包含的作用域称为内层作用域,包含别的作用域的作用域称为外层作用域。作用域中一旦声明了某个变量,那它所嵌套着的所有作用域中都能访问该名字,同时允许在内层作用域中重新定义外层作用域已有的名字。
注意:但是若函数有可能用到某个全局变量,则不宜再定义一个同名的局部变量。
29、复合类型
是指基于其他类型定义的类型,如引用和指针。
30、引用
是为对象起了另外一个名字,引用类型引用另外一种类型。引用必须被初始化。
int nVal = 24;
int& nRetVal = nVal; //nRetVal指向nVal
一般在初始化变量时,初始值会被拷贝到新建的对象中。但是定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成之后,引用将会和它的初始值对象一直绑定在一起,因为没有办法让引用重新绑定到另外一个对象,因此引用必须初始化。
①给引用赋值,实际上是把值赋给了和引用绑定的对象,获取引用的值,实际上是获取和引用绑定的对象的值。引用本身不是一个对象,因此不能定义引用的引用。
②允许在一条语句中定义多个引用,每个引用标识符都必须以"&"开头:
int i = 12, j = 23;
int &k = i, m = j;
int &g = j, &y = i;
③引用只能绑定在对象上,不能和字面值或某个表达式的计算结果绑定在一起,类型保持一致。
int i = 4, &r1 = i;
double d = 3, &r2 = d;
r2 = 3.14159265; //实际上赋值给了d
r2 = r1; //实际上赋值给了d
i = r2; //不能将引用赋值给其他对象
r1 = d; //实际上赋值给了i
31、指针
是“指向”另外一种类型的复合类型,指针和引用类似,实现了对其他对象的间接访问。指针和引用的区别:①指针本身是一个对象,允许对指针赋值和拷贝,且在指针的生命周期内可以先后指向几个不同的对象;②指针无须在定义时赋初值。
char *p1, p2; //p1是指向char类型对象的指针,p2是char型对象
char *p1, *p2; //p1额和p2都是指向char型对象的指针
1)指针存放某个对象的地址,若想获取该地址,需要使用取地址符(&)。在声明语句中指针的类型实际上被用于指定它所指向对象的类型,所以二者必须匹配,若指针指向了一个其他类型的对象,对该对象的操作将发生错误。
2)指针的值(即地址)应属于下列4种状态之一:
①指向一个对象
②指向紧邻对象所占空间的下一位置
③空指针,说明指针没有指向任何对象
④无效指针,即上述情况之外的情况
试图拷贝或以其他方式访问无效指针的值都是会引发错误的,编译器不会检查这一类的错误,因此在使用指针之前,我们最好先确认一下指针是否有效。
3)利用指针访问对象
若指针指向一个对象,允许使用解引用符(*)访问对象。对指针解引用会得到所指的对象,因此如果给解引用的结果赋值,实际上就是给指针所指的对象赋值
int nVal = 42;
int *pVal = &nVal; //pVal存放nVal的地址
std::cout << *pVal; //输出指针pVal所指的对象
*pVal = 23; // 给指针pVal所指的对象赋值
std::cout <<" " << *pVal;
输出结果是42 23。
注意:解引用操作仅适用于那些确实指向了某个对象的有效指针。
4)空指针
空指针不指向任何对象,在试图使用一个指针之前可以先检查是否为空。生成空指针的方法如下:
char* p1 = nullptr;
char* p1 = 0;
char* p1 = NULL;
最直接的方法是使用字面值nullptr,这是C++ 11引入的一种方法。nullptr是一种特殊的字面值,可以vein转换成任意其他类型的指针。NULL是一个预处理变量,其不属于命名空间std,由预处理器负责管理,因此可以直接使用而无须在前面加上std::,预处理器会自动地将预处理变量替换成实际值。
5)对于2个类型相同的合法指针,可以用==或!=进行比较,若指针存放的地址值相同,则相等。指针存放的地址值相同有3种可能:
①都为空
②都指向同一个对象
③都指向同一个对象的下一个地址
④一个指针指向某对象,同时另一指针指向另外对象的下一地址,也可能是相等的
int nVal = 2, nCount = 2;
int *p1 = &nVal;
int *p2 = &nCount;
if (p2 == p1)
{
std::cout << "p2 == p1" << std::endl;
}
else
{
std::cout << "p2 != p1" << std::endl;
}
if (*p1 == *p2)
{
std::cout << "*p2 == *p1" << std::endl;
}
else
{
std::cout << "*p2 != *p1" << std::endl;
}
输出结果是:
p2 != p1
*p2 == *p1
32、void* 指针
这是一种特殊的指针,可用于存放任意对象的地址。不能直接操作void*指针所指的对象,因为不知道该对象的类型,也就不确定该对象能支持哪些操作。
33、指向指针的指针
指针是内存中的对象,对象能有自己的地址,因此允许把指针的地址再存放到另一个指针中。根据*的个数区分指针的级别,如“**”表示指向指针的指针,为了访问最原始的那个对象,需要对指针进行对应次数的解引用。
int nVal = 2;
int *p1 = &nVal;
int ** pp1 = &p1;
int ***ppp1 = &pp1;
std::cout << "*p1: " << *p1 << " **pp1: " <<**pp1 << " ***ppp1: " << ***ppp1 <<std::endl;
1)指向指针的引用
指针是对象,因此存在对指针的引用:
int nVal = 2;
int *p1 = &nVal;
int *&pRef = p1; //pRef是一个对指针p1的引用
std::cout << "*p1: " << *p1 << " *pRef: " << *pRef << std::endl;
int nCount = 101;
pRef = &nCount;
std::cout << "*p1: " << *p1 << " *pRef: " << *pRef << std::endl;
*pRef = 68;
std::cout << "*p1: " << *p1 << " *pRef: " << *pRef << std::endl;
注意:面对一条比较复杂的指针或引用的声明语句时,从右向左更能理解真实含义。