1 关于程序
1.1 “编译器/编辑器/解释器”
1.1.1 IDE-集成开发环境
IDE这个概念相比大家并不陌生,像:Visual Studio、intellij idea、PyCharm等,都属于IDE,那么什么使IDE呢?其全称是“Integrated Development Environment ”,中文名叫做“集成开发环境”。百度给出的解释如下,
集成开发环境(IDE,Integrated Development Environment )是用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面等工具。集成了代码编写功能、分析功能、编译功能、调试功能等一体化的开发软件服务套。所有具备这一特性的软件或者软件套(组)都可以叫集成开发环境
注意这里的几个关键句:“一般包括代码编辑器、编译器、调试器和图形用户界面等工具”,“。集成了代码编写功能、分析功能、编译功能、调试功能等”。
1.1.2 代码编辑器
最简单的代码编辑器就是记事本了。它与文档编辑器(文字处理器)不同之处在于它并非用作桌面排版(例如文档格式处理),它常用来编写程序的源代码。
常用的代码编辑器,如:Notepad++,EditPlus,vim,Sublime Text,WebStorm ,HBuilder,GNU Emacs,ATOM,Windows记事本等,常用的visual studio code也是微软开发一款开源文本编辑器。
1.1.3 编译器
什么是编译(Compile)?就是将在代码编辑器中使用高级编程语言,像:Java、C++、Python等编写的源代码,使用编译器“翻译”成为计算机可识别的二进制代码,因为计算机只能够识别0和1。
而编译器的作用就是去执行源代码的编译工作,即:编译器是把源代码整个编译成目标代码,执行时不在需要编译器,直接在支持目标代码的平台上运行。像C语言嗲码就可以被编译成为二进制代码,成为“exe可执行程序”,从而直接在Windows平台上运行。
从执行过程上来看,编译器在对源码进行编译的过程中,可以对源码进行语法检查,并生成中间代码,最终将中间代码进行整合,经链接器得到最终的可执行的机器码。
1.1.4 调试器
无论是前端开发还是后端代码编写,调试器都是必不可少的工具,因为它能够帮助程序员发现程序中是在哪一块内容发生异常,导致不正常的运行结果。关于调试器是如何工作的?可以看一下知乎上的这篇文章:https://zhuanlan.zhihu.com/p/40079495
1.1.5 图形用户界面
图形用户界面,Graphical User Interface,简称 GUI,又称图形用户接口,是指采用图形方式显示的计算机操作用户界面。现在我们使用的Windows系统、以及Linux系统都搭载有图形用户界面,目的是:可以让使用者通过鼠标交互的方式进行计算机操作,使其更加大众化,受用面更广。
与之相对的是命令行(commandLine)交互方式,像C/C++/Java/Python/C#以及其它高级语言的初学者,都会经历和命令行长时间接触的痛苦过程,因为它没有按钮、无法使用鼠标进行随心所欲的交互。
1 C++概述
C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。因此,C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点。 C++ 完全支持面向对象的程序设计,包括面向对象开发的四大特性:
封装
抽象
继承
多态
1.0 关于C++发展历史
可以自行百度了解。
1.1 第一个C++程序
此处采用DevC++编译器,新建项目,并编写如下代码,
#include
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
std::cout<
return 0;
}
点击工具栏的编译与运行(Compile&运行)按钮,执行程序的编译与二进制程序的运行,出现如下结果即为运行成功。
1.2 对第一个C++程序的理解
1.2.1 代码的理解
1.2.1.1 关于头文件包含:#include
头文件引入代码如下, #include
C++中的头文件相当于C语言中的.h文件,相当于Java中的*.Java(依赖)文件,相当于C#中的 .cs 文件,相当于JavaScript中的*.js文件,相当于Python中的*.py文件。总结一句话,就是:被“#include”的头文件是被当前程序代码所需要的,如果不引入,就可能造成程序无法运行(考虑到部分引入了无用头文件的情况)。
iostream头文件是C++的标准库之一,其内部定义了命名空间std、各种输入流、输出流,以及相关函数的头文件包含命令,内容如下,
#ifndef _GLIBCXX_IOSTREAM
#define _GLIBCXX_IOSTREAM 1
#pragma GCC system_header
#include
#include
#include
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
/**
* @name Standard Stream Objects
*
* The <iostream> header declares the eight standard stream
* objects. For other declarations, see
* http://gcc.gnu.org/onlinedocs/libstdc++/manual/io.html
* and the @link iosfwd I/O forward declarations @endlink
*
* They are required by default to cooperate with the global C
* library's @c FILE streams, and to be available during program
* startup and termination. For more information, see the section of the
* manual linked to above.
*/
//@{
extern istream cin;/// Linked to standard input
extern ostream cout;/// Linked to standard output
extern ostream cerr;/// Linked to standard error (unbuffered)
extern ostream clog;/// Linked to standard error (buffered)
#ifdef _GLIBCXX_USE_WCHAR_T
extern wistream wcin;/// Linked to standard input
extern wostream wcout;/// Linked to standard output
extern wostream wcerr;/// Linked to standard error (unbuffered)
extern wostream wclog;/// Linked to standard error (buffered)
#endif
//@}
// For construction of filebuffers for cout, cin, cerr, clog et. al.
static ios_base::Init __ioinit;
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
#endif /* _GLIBCXX_IOSTREAM */
例如,下面是用到的输出流对象-cout就被定义在这个文件中,以及出入流对象-cin。
通过头文件包含,可以利用已有的代码库进行程序的快速开发,而无需关心底层实现,把主要精力花费在程序的逻辑代码设计上。
PS:关于iostream头文件,想了解更多阅读源码,也可以参考博客:https://www.cnblogs.com/jikexianfeng/articles/5651661.html
1.2.1.2 关于使用命名空间using namespace
C++的标准命名空间为std,内部包含了很多标准的对象/变量定义。其具体作用可以阅读:http://c.biancheng.net/view/2192.html
1.2.1.3 关于main函数的标准写法
虽然C++是由C演变过来的,但是两者之间还是存在很大的差异。对于main()函数来说, C++中允许两种格式,带参数的和不带参数的:
int main()
int main(int argc, const char* argv[])
和C标准不同,C++中main函数必须写明返回类型为int,不支持main默认返回int类型这一规定。
同时和C标准一样,若main函数中没有返回语句,那么最后默认添加上return 0;语句。
1.2.2 编译过程的理解
C++程序的整个编译过程分为两大步:
1).编译 :把文本形式的源代码翻译成机器语言,并形成目标文件
2)连接 :把目标文件 操作系统的启动代码和库文件组织起来形成可执行程序
1.2.2.1 编译
可细分为3个阶段,
(1)预编译阶段。主要是做些代码文本替换工作。编译器执行预处理指令(头文件包含、条件编译和宏定义),这个过程会得到不包含#指令的.i文件。这个过程会拷贝#include 包含的文件代码,进行#define 宏定义的替换 , 处理条件编译指令 (#ifndef #ifdef #endif)等
(2)编译优化。通过预编译输出的.i文件中,只有常量:数字、字符串、变量的定义,以及c语言的关键字:main、if、else、for、while等。这阶段要做的工作主要是,通过语法分析和词法分析,确定所有指令是否符合规则,之后翻译成汇编代码。这个过程将.i文件转化位.s文件。
(3)汇编。汇编过程就是把汇编语言翻译成目标机器指令的过程,生成目标文件(.obj .o等)。目标文件中存放的也就是与源程序等效的目标的机器语言代码。这个过程将.s文件转化成.o文件。 PS:目标文件由段组成,通常至少有两个段:
①代码段:包换主要程序的指令。该段是可读和可执行的,一般不可写
②数据段:存放程序用到的全局变量或静态数据。可读、可写、可执行。
1.2.2.2 连接
由汇编程序生成的目标文件并不能立即就执行,还要通过链接过程。原因是:
1).某个源文件调用了另一个源文件中的函数或常量
2).在程序中调用了某个库文件中的函数 链接程序的主要工作就是将有关的目标文件连接起来,这个过程将.o文件转化成可执行的文件。
1.3 C++中的代码注释
注释的作用是:增强代码的可读性。可分为单行注释和多行注释,
(1)单行注释://注释内容
(2)多行注释:/*注释内容*/
1.4 变量&常量
1.4.1 变量
变量,实质上代表一块内存空间,用于存放一个具体的值,称之为变量的值。
(1)创建语法: 数据类型 变量名称 = 变量初始值; ((2)如图1.1.1-3所示,可创建变量a,初始值为10。即: int a = 10;
1.4.2 常量
常量,是用于记录程序运行过程中不可被更改的数据。C++中定义常量的两种方式,
(1)#define 宏常量:#define 常量名 常量值
(2)const修饰的变量:const 数据类型 常量名 = 常量值;
如果在程序中尝试修改常量的值,程序就会报错,
1.5 关键字
关键字,是C++中预先保留的单词。在定义变量或者常量时,不能使用关键字作为其名称。
1.6 C++数据类型
数据类型存在的意义:在创建变量时,为变量分配合适的内存空间。C++中的数据类型可分为以下几类:基本数据类型(空类型void、整型、浮点型、字符型、布尔型)、结构体、指针类型、数组和类。
1.6.1 基本数据类型
再扒拉一张知乎上的图片,如下所示为C++中的基本数据类型,
ISOC++标准并每日有明确规定每一种基本数据类型所占的字节数,对于不同操作系统,使用sizeof的计算结果是不同的,而仅仅规定了它们之间的字节数大小顺序满足:
(signed/unsigned)char≤(unsigned)short≤(unsigned)int≤(unsigned)long
在64位Win10系统下,所占的字节数如下所示,
#include
using namespace std;
int main(int argc, char** argv) {
//int :4 byte
std::cout<
std::cout<
std::cout<
//long :4 byte
std::cout<
std::cout<
std::cout<
//float :4 byte
std::cout<
//double :8byte
std::cout<
//char :1byte
std::cout<
std::cout<
std::cout<
return 0;
}
1.6.2 数组类型
数组就是存放了指定个数的、相同数据类型的数据元素的集合。具有以下两个特点,
特点①:数组中的每个数据元素的数据类型都是相同的;
特点②:数组中的数据元素在内存中的存储位置是连续的,且和元素的逻辑顺序一一对应,属于线性存储结构。
数组按照维度可以分为一维数组和多维数组。下面主要介绍一维数组和二维数组。
1.6.2.1 一维数组
先看一下一维数组的定义方式,
①数据类型 数组名【数组长度】;
②数据类型 数组名【数组长度】={ 元素值1,元素值2,…元素值n };
③数据类型 数组名【】={ 元素值1,元素值2,…元素值n };
下面定义一个整型数组,并通过冒泡排序进行元素的从小到大排列。
#include
using namespace std;
void bubbleSort(int* arr,int len);
void printArray(const int* arr,int len);
int main(int argc,char** argv){
//array
int arr[]={5,6,0,2,9,4,1,12,65};
int len=sizeof(arr)/sizeof(int);
//use functions
printArray(arr,len);
bubbleSort(arr,len);
printArray(arr,len);
return 0;
}
void bubbleSort(int* arr,int len){
int i,j,temp;
for(i=0;i
for(j=0;j
if(arr[j]>arr[j+1]){
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
void printArray(const int* arr,int len){
for(int j=0;j
std::cout<
std::cout<<:endl>
}
输出结果如下所示,
1.6.2.1 二维数组
二维数组实质上就是一维数组的嵌套,其定义方式为,(建议使用定义方式②)
①数据类型 数组名【行数】【列数】;
②数据类型 数组名【行数】【列数】 = {{数据1,数据2…},{数据3,数据4}};
③数据类型 数组名【行数】【列数】 = {数据1,数据2,数据3,数据4…};
④数据类型 数组名【 】【列数】 = {数据1,数据2,数据3,数据4…};
对于二维数组元素的地址遍历查询如下,
void TwoDArray(){
int arr[][2]={1,2,3,4,5,6};
for(int i=0;i
{
for(int j=0;j
{
std::cout<
}
std::cout<
}
}
1.6.3 结构体
结构体从本质上来说,就是基本数据类型的嵌套定义,属于用户自定义的数据类型,并且允许用户存储不同的数据类型。
1.6.3.1 结构体的定义和使用
结构体的定义形式如下,
struct 结构体名{ 结构体成员列表 };
例如,定义一个员工的结构体,其属性包括姓名,年龄,部门,员工号4个。通过输入流对象创建结构体变量,并打印该变量的信息。
#include
#include
using namespace std;
//struct
struct Employee{
int empID;
string name;
int age;
string deptName;
};
void printEmployeeObj(const Employee* e){
std::cout<empID<name<age<deptName<<:endl>
}
int main(int argc,char** argv){
//struct variable
struct Employee em;//{1,"张三",24,"销售部"}
std::cout<
std::cin>>em.empID>>em.name>>em.age>>em.deptName;
printEmployeeObj(&em);
return 0;
}
1.6.3.2 结构体数组
虽然结构体是自定义数据类型,但是C++也支持创建自定义数据类型的数组,即“结构体数组”,其实质上是:将结构体变量存放到数组中进行统一的管理。语法格式如下,
struct 结构体名[数组长度] = {结构体变量1,结构体变量2,结构体变量3…};
1.6.4 指针类型
1.6.4.1 指针的基本概念
什么是指针?指针就是地址,它用于标记一块内存空间的起始位置。
指针有什么作用?可以通过指针来间接访问一块内存区域。如果将指针作为函数的参数进行传递,那么程序的执行效率也会大大提升。
如何理解内存与指针之间的关系?根据冯诺依曼提出的“存储程序”的计算及工作原理,程序要装入内存后才能运行,数据也要装入内存后才能进行处理。而这里的内存就是以字节为单位的一片连续的存储空间,每一个字节所占的位置都有一个唯一的编号,这个编号就叫做这个字节所在的“地址”,也即“指针”。指针/地址通常是从0开始的,顺序连续进行编号。
C++程序如何访问指针/地址?可以采用直接访问或者间接访问的方式。
【1】直接访问:C/C++编译器在将源程序转换为目标程序(*.obj)时,将变量名转换为对某一个存储单元的访问。因此,程序中通过变量名对变量值的访问,实质上就是对这个变量所对应的内存单元的访问。这种直接按照内存地址访问变量的方式就称之为“直接访问”。
【2】间接访问:将要被访问的变量所在的地址值赋值给指针变量,访问时,先使用解引用运算符*,从指针变量中取出地址值,再按照地址值去访问对应的存储单元,被称之为“间接访问”。
如何使用指针?可以利用指针变量保存地址,然后通过地址值去修改变量值。即:指针变量可以指向具有特定数据类型的变量,也可以通过动态存储分配(new)的方式指向内存中的一块连续区域。
1.6.4.2 指针变量的定义和使用
(1)指针变量的定义形式如下所示,
数据类型* 指针变量名;
例如: int * p = &a;
PS:这里,指针变量的类型为:int*;
(2)指针使用时,可以通过解引用的方式,来获取指针指向内存中的数据。解引用的格式为,
*指针变量名;
1.6.4.3 特别说明:&和*的运算符优先级
取地址运算符为&,解引用运算符为*,
&和*两个运算符具有相同的优先级,按自右向左的方向结合。
[1]&:取地址运算符,用来获取变量所在的内存单元地址4值(即:该变量所占内存单元的第一个字节对用的地址);
[2]*:解引用运算符,用来获取该指针所指向的变量(内存区域)。
例如:假设定义了一个指针变量p指向a,那么,
①计算“&*p”时,先计算*p,得到代表存储单元的变量a,再取其地址,得到a的地址,即:&*p等价于&a;
②计算“*&a”时,先获取变量a的地址,在取出地址中的值,即:*&a等价于*p。
1.6.4.4 指针所占的内存空间
(1)在32位操作系统下,占用4个字节的内存空间;
(2)在64位操作系统下,占用8个字节的内存空间。
1.6.4.5 空指针和野指针
(1)空指针:指针变量指向的内存空间地址为0的内存,则称该指针位空指针。其作用是:用于初始化指针变量,即:将指针变量置为空NULL(实质上是使其指向地址为0的内存区域)。
※注意:空指针指向的内存是不可以访问的,即:内存为0的地址是不可被访问的。
(2)野指针:指针变量指向非法的内存空间,则称该指针变量为野指针。通常是由于直接通过指针变量是直接操作未经申请的内存空间造成程序报错,即:对野指针的非法操作会造成程序异常终止甚至直接崩溃。如下所示,
※解读:代码中直接将0x1100强转为int*指针类型,将其地址赋给指针变量p,但是,由于该地址未经过程序申请,所以在通过解引用去操作该指针变量-地址,就会报错。 所以,在使用指针间接操作内存地址时,一定要由程序员自行申请内存空间,然后才可对其进行操作。
1.6.4.6 被const关键字修饰的指针变量
关键字const修饰的指针有三种情况,
①const修饰指针:常量指针;
const int* p = &a;
特点:指针的指向可以改变,但是指针指向的值不可以修改。
例如:定义打印数组的函数为“printfArray(const int* arr,int len)”,那么,就只能对数组元素进行读取,而不能在执行数组元素的修改/写入操作。
②const修饰常量:指针常量;
int* const p = &a;
特点:指针的指向不可以改,但是指针指向的值可以修改。
③const既修饰指针,又修饰常量。
const int* const p = &a;
特点:指针的指向、指针指向的值都不可修改。
1.6.4.7 指针和数组
目的:利用指针,访问数组中的元素。说明如下:
①若int型指针变量p指向一个整型数组arr,即:int* p=arr,那么:“p+1”中的1表示一个存储单元的长度。这里的存储单元长度和基类型-int型变量对应的长度保持一致。
②C/C++中的数组名默认为常量,不可用数组名进行自加运算。
1.6.5 类
类和结构体一样,都属于自定义数据类型,这部分到类和对象的部分在进行详述。
1.7 运算符
C++中的运算符主要可分为:算术运算符、赋值运算符
1.7.1 算术运算符
主要用于处理四则混合运算,
1.7.2 赋值运算符
主要用于将表达式的值赋给变量。
1.7.3 比较运算符
主要用于表达式的比较,返回一个逻辑值(真或假)。
1.7.4 逻辑运算符
主要用于根据表达式的值返回逻辑值(真或假)。
PS:短路和逻辑的区别,
①&&-短路与、||短路或,只要有一个条件满足表达式,就不再往下执行;
②&-逻辑与、|逻辑或,对所有表达式都进行计算。
1.8 程序流程结构
C/C++支持的三种基本程序运行结构:顺序结构、选择结构、循环结构。几乎所有的高级编程语言都以不同的形式支持这3种流程结构,故不再进行过多赘述。
顺序结构:程序按顺序执行,不发生就跳转;
选择结构:依据是否满足条件,有选择地执行相应代码;
循环结构:依据是否满足条件,循环多次执行某段代码。
1.8.1 选择结构
(1)if语句
(2)三目运算符:通过三目运算符可实现简单的判断。 表达式1?表达式2:表达式3
(3)Switch…case…语句
PS:
[1]如果case语句没有break,则会一直向下执行;
[2]与if选择结构相比,switch…case…结构清晰,执行效率高,但是不能用于判断区间。
1.8.2 循环结构
(1)while循环语句
(2)do…while…循环语句
(3)for循环语句
for(起始表达式;条件表达式;末尾表达式) { 循环语句; }
1.9 函数
1.9.1 函数的定义
定义形式如下,
返回值类型 函数名(形参列表){
//函数体
}
1.9.2 函数的调用
函数在调用时,有两种最基本的参数传递方案,
1.9.2.1 值传递
(1)值传递
若形参发生变化,则不会影响实参的值。
原因:在main函数调用swap(int,int)函数时,会自动为swap(int,int)函数的形式参数分配新的、独立的内存单元,然后将实参的值赋给形参。因此,之后对形参的各种操作就与实参无关。
void swap(int a,int b){
std::cout<
int temp=a;
a=b;
b=temp;
}
int main(int argc, char** argv) {
int a=12,b=10;
swap(a,b);
std::cout<
system("pause");
return 0;
}
图解如下,
1.9.2.2 引用传递
(2)址传递
若形参发生变化,实参的值也会被修改。
原因:在main函数调用sway(int,int)函数时,并不会为函数swap(int,int)函数的形式参数分配新的内存空间,而是会将实际参数的地址赋给形式参数,从而让形式参数和实际参数共享一块内存空间。因此,之后对形参的各种操作就会影响实际参数的值。
void swapPointer(int* a,int* b){
std::cout<
int temp=*a;
*a=*b;
*b=temp;
}
int main(int argc, char** argv) {
int a=12,b=10;
swap(a,b);
std::cout<
system("pause");
return 0;
}
图解如下,