C++基础笔记
前言
面向过程和面向对象
面向过程编程POP
- 以过程为中心的编程范式
- 按照计算机执行的步骤,从上到下顺序设计程序
面向对象编程OOP
- 以对象为核心的编程范式
- 对象是类的实例,类中包括了数据的定义和对数据的操作方法
编译型语言和解释型语言
- 编译:由编译器把整个源代码翻译成机器码,最终生成二进制文件,一次性提交给计算机执行,如C、C++。
- 解释:由解释器将代码逐行解释成机器码,并交给计算机执行,即脚本语言,如Python、JavaScript。
进入C++
C++预处理器和iostream文件
#include <iostream>
using namespace std;
/*#include <iostream>该编译指令导致预处理器
将iostream文件加入程序*/
实际上,#include编译指令将导致iostream文件的内容随源代码文件一起交给编译器,并且 #include <iostream>
指令将被iostream文件的内容替代和源代码一起组成一个复合文件
头文件
像iostream这样的文件叫做包含文件,由于它们被包含在其他文件中,也叫头文件——由于他们被包含在文件起始处。头文件使用扩展名h,C++新式风格没有h扩展名。
头文件书写有自己的规范,对于complex.h
#idndef _COMPLEX_
#define _COMPLEX_
//头文件内容
#endif
名称空间
如果使用iostream而非iostream.h,则应该使用下面的名称空间编译指令 using namespace std;
。这是using编译指令。名称空间支持是C++一大特性,这里存在一个问题:对于两个已经封装好的C++文件,它们都包含同一函数名的函数,当使用此函数时,编译器其实不知道具体指的是哪个版本的函数。名称空间使得文件内容封装在一个名称空间的单元中,这样就可以用名称空间的名称来指出具体某个文件的具体函数。
示例:
Bob::sayHi();
John::sayHi();
按照此方式来讲,类、函数、变量就是C++编译器的标准组件,它们现在都存放在名称空间std中。这意味着iostream中定义的用于输出的cout变量实际是 std::cout
,而endl实际是 std::endl
。而当使用using编译指令后,就可以使用std名称空间中定义的名称而不用加 std::
前缀。
四种访问名称空间std的方法:
- 将
using namespace std;
放在函数定义之前,让文件中所有函数能够使用名称空间std中所有的元素 - 将
using namespace std;
放在特定函数定义中,让该函数能够使用名称空间std中所有元素 - 在特定的函数中使用类似
using std::cout;
这种编译指令,而不是使用using namespace std;
,让该函数能够使用特定的元素如cout
- 完全不使用编译指令
using
,而在需要使用名称空间std中元素时使用前缀std::
。
变量名称规则
C++提倡使用一定含义的变量名,以下是命名规则:
- 名称中只能使用字母字符、数字和下划线;
- 名称的第一个字符不能是数字;
- 区分大小写;
- 不能用关键字作为名称;
- 以两个下划线开头(如:
__xxxx
或者_大写字母xxx
)或者以下划线和大写字母打头的名称将被保留(留与编译器或其使用资源使用),以一个下划线开头的名称将被保留(用作全局标识符)。
常量
不可更改的变量数据即常量,在C++中有两种方式定义常量。
使用符号常量
在文件头中使用#define
定义常量,也称作“宏定义”。
#define ZERO 0
类似于#include
,#
开头的语句都是预处理语句,在编译之前预处理器会查找程序中所有的ZERO
,并将其替换成0。这种宏定义的方式是保留C语言特性,在C++中一般不推荐。
使用const限定符
只需要变量的数据类型前加const
关键字即可。
const int Zero = 0;
const修饰的对象一旦创建就不能修改,所以必须要初始化。
与使用#define
定义宏常量相比,const定义的常量有详细的数据类型,而且在编译阶段进行安全检查,在运行时才完成替换,更加安全和方便。
复合数据类型
模板类Vector
vector是标准库的一部分,使用时必须要包含<vector>
头文件,并使用std命名空间。
#include <vector>
using namespace std;
在Vector头文件中,对vector这种类型做了定义。使用#include
引入并指定命名空间std后,就可以使用vector。
基本用法
vector是C++的一个类模板,使用时必须提供具体的类型信息。
vector<int> v;
初始化
- 列表(拷贝)初始化
C++vector<char> v1 = {'a','b','c'}; vector<char> v2{'a','b','c'};
- 直接初始化
C++vector<short> v3(5); vector<long> v4(5,100);
基于对象
构造函数
内联函数
内联函数是C++为了提高程序运行速度所做的一项改进,一般函数和内联函数之间主要区别不在于编写方式,而在于C++编译器如何将它们组合到程序中。通常,在执行一般函数的调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈,然后跳到标记函数起点的内存单元,执行函数后,再跳回地址被保存的指令处(即原位置),程序将会来回跳跃地址,这需要一定的时间开销。而C++内联函数的编译代码与其他代码内联起来,编译器将使用相应函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码再跳回,因此内联函数运行速度更快,但需要更多内存。
如果要使用内联函数,必须:
- 在函数声明前加上inline
- 或在函数定义前加上inline
注意:内联函数不能递归;函数过大或者函数调用了自己,则编译器不会将其作为内联函数
内联与宏
inline是C++新特性,C使用预处理语句#define来提供宏——内联代码的原始实现
示例:
#define ADD(X) X+X
这并不是通过传参来实现,而是通过文本替换,X是参数的符号标记
sum_a=ADD(3);//等价于sum_a=3+3;
sum_b=ADD(3-2);//等价于sum_b=3-2+3-2;
sum_c=ADD(c--);//等价于sum_c=c--+c--;
上述代码当然可以通过添加适当的括号改进类似于 #define ADD(X) ((X)+(X))
,但是因为宏并不能按值传递,则 ADD(c--)
中的c变量依然会被调用两次递减两次,这和预期并不同,因此我们可以考虑将宏函数转换成内联函数使用。