一 C++基础

C/C++基础

  • 一 基本概念
    • 1 程序与语言
    • 2 C语言
    • 3 算法
    • 4 数据结构
  • 二 变量与数据类型
    • 1 变量
    • 2 数据细节
    • 3 输入输出
    • 4 变量作用域&生命周期
    • 5 指针类型
    • 6 const限定符
    • 7 表达式与运算符
    • 8其它类型
  • 三 数组与函数
    • 1 String字符串
    • 2 vector容器
    • 3 数组
    • 4 多维数组
    • 5 函 数
  • 四 结构化模块
    • 文件结构

一 基本概念

基本内容介绍

1 程序与语言

(1) 程序
一组计算机能识别的指令,命令。执行对应的操作。没有指令,计算机不执行任何操作。

(2) 语言
人要操控计算机,需要一种二者都能直接识别的话,经历了不同阶段发展

类型 介绍
机器语言 计算机能直接识别的01二进制码,这些二进制码的集合,就叫机器语言
符号/汇编语言 人把一些操作,简化为符号表达出来,简单好记一些
高级语言 接近人习惯使用的自然语言和数学语言,容易理解
  • 高级语言分类:
    • 非结构:风格随意,流程随便跳转。难以阅读(基于过程)
    • 结构:基本结构-顺序,选择,循环。规定必须从上而下执行,如C,(基于过程,要求细节,只适用小规模程序)
    • 面向对象:不面向细节,而是针对一个个由数据以及对数据的操作所组成的对象,支持大规模程序问题。

(3) 编译
把高级语言写的程序,翻译为机器指令,然后计算机便可以执行。这一过常称为编译,然后计算机便可以根据指令,得到结果。

  • 编译过程:
  • 编译代码文件,会对每一个cpp文件生成对应的obj文件,然后编译器会将这些obj文件链接(link)起来,生成可执行文件。

注意:值得一提.obj:是汇编程序


2 C语言

(1) 定义
一种通用的、高效的编程语言,具有简洁的语法和强大的底层控制能力

特点 介绍
简洁 紧凑灵活,仅必要内核。输入输出与文件操作,内存管理都由编译系统(GCC)提供的库函数实现
结构化编程 语法限制不严,设计自由度高
以函数为基本单位 程序几乎所有部分都由函数完成,函数是C的基本单位,C就是编写一个个函数
模块化设计 以程序文件为对象进行编译,一个cpp是一个编译单位,
指针系统 直接操作内存地址,实现高效数据结构和内存管理

应用:操作系统开发(Linux内核、Windows驱动),嵌入式系统(物联网设备、微控制器),高性能计算(算法核心、游戏引擎)

(2) 程序运行过程
编辑程序(C编写代码)——>编译源程序(高级语言翻译为机器指令)——>源程序变为二进制目标程序(机器指令程序.obj)——>链接生成可执行程序(编译得到的机器指令模块,与函数库相连)

程序设计过程:问题分析——>设计算法(解题方法于步骤)——>编辑程序,编译,改错


3 算法

(1) 定义
在C程序设计中,算法是解决问题的明确步骤集合,是程序设计的核心,决定了程序的逻辑结构、效率与可靠性。(逻辑,思路,解决问题的方案)

(2) 设计工具

  • 普通流程图:以图形化方式描述算法或工作流程的工具,通过标准化的符号系统直观展示程序的执行逻辑,是C语言等编程中设计与分析的手段。
  • N-S结构化流程图:简称盒图,是一种完全结构化的图形化算法表示工具。
    一 C++基础_第1张图片

文章:算法基础整合

算法已经与人类知的识体系深度结合


4 数据结构

(1) 定义
数据结构:是计算机存储、组织数据的特定方式,旨在实现高效访问与修改操作。其本质是现实问题到计算机模型的抽象映射

(2) 分类
依逻辑结构可简单分类:

类型 典型数据结构
线性结构 数组、链表、栈、队列、哈希表
树形结构 二叉树、堆、B树、Trie树、红黑树
图形结构 邻接矩阵、邻接表、十字链表
集合结构 集合(Set)、位图(Bitmap)

注意:算法与数据结构相互依存,算法必须通过数据结构操作数据


二 变量与数据类型

本章介绍:变量与基本类型,数据细节,内存与指针,输入与输出,表达式与运算符

1 变量

(1) 定义

  • 提供一个可供操作的,有名的存储空间,用于存储数据。数据的类型决定了存储空间大小和布局方式。
  • 对象:指一块能存储和数据且具有数据类型的内存空间,对象变量一般可互换。
  • 声明:如 extern int a,变量可以被声明多次,用于在不同的文件中使用,但不显示的初始化变量。
  • 初始化:不同于赋值,前者是创建对象时并获得一个初始值,后者是移除当前值用新值替代

(2) 命名规范

  • 用字母或下划线开头
  • 变量名用小写字母
  • 多个单词用下划线连接,不要一团
  • 自定义的类名用大写字母开头

(3) 基本数据类型
一 C++基础_第2张图片

long的长度依赖编译器和操作系统(Windows通常4字节,Linux x64通常8字节)

(4) 初始化

  • 定义在函数体内部的内置变量将不被默认初始化
  • 定义变量本质:基本数据类型+一组声明符,类型修饰符不过是声明符的一部分而已。
  • 定义变量时没有赋初值,则变量会默认初始化。
    • 若为内置类型(整形和浮点型,包括字符和布尔),则它的默认初始化的值根据位置决定:
    • 定义在函数体之外的变量被默认初始化为0(包括main函数!!),
    • 函数体内的内置类型变量不被初始化,值是未知的。
例如:
1 int a=5,声明符就是变量名a,但还存在复杂的声明符,如引用和指针。
2 int &b=a,*p=&a,声明了引用类型变量 b,和指针类型变量 p

其中声明符分别为:&b 和 *p
&* 不过是各自声明符的一部分。
2中这种一个基本类型数据类型(int+一组声明符(多个不同类型的变量,引用类型指针类型)的声明方式是完全合法的。

如:在main函数定义:int a;a的值是未定义的。通过拷贝或其他形式访问a的值j就会出现错误


2 数据细节

(1) 正数与负数的存储

  • 正数:在计算机中以二进制原码形存储,正数原,反,补码皆为本身
  • 负数:在计算机中并不是以原码形存储的,而是以补码存储的。也可以说,所有二进制数在计算机中都用补码来进行存取
    • 负数原码:符号位是1,其余位为正数原码
    • 负数反码:符号位为1,其余位取反(0变1,1变0)
    • 负数补码:符号位为1,其余位取反,然后+1(即补码是原码取反在+1)

注意:
有符号整数进行加减运算,实际上是采用数据的补码进行加法运算,得出的结果为补码形式
即:正数补码就是原码,负数减1再取反可得负数原码。

(2)一字节大小数据的数值范围

  • C的数据类型中,包含有符号数据类型 和 无符号数据类型
  • 无符号一字节数值范围:0~255
  • 有符号:
    • 正数可用0 000 0001~0 111 1111 及:1—127
    • 负数可用1 111 1111~1000 0001 及:-1----127
    • 由于0有+0和-0,而-0为:1000 0000 代表-128
    • (有符号类型,若进为到达符号位则舍弃) 故,有符号范围:-128~127

注:掌握细节,了解边界情况

(3) 字面值常量

  • 定义:形如35这种没有名字直接用他们的值来称呼的常量,统称为字面值常量
  • 包括整数、小数、字符和字符串,一般赋值时使用
其常见数据类型为:

1 十进制:最小匹配intlong,longlong,(默认带符号)
2 八进制十六进制:最小匹配int,u~intlong,u-long,longlong,u-longlong
3 浮点型:字面值为double,表现形式:3.  , .3  , 0.3e1(科学计数法,0.3*104 字符型:char
5 字符串:arry,末尾加’\0‘,故实际长度+1
6 转义字符:不可打印,特殊含义,用‘\’+字母,或‘\’+八进制(最多3个)或‘\x’+16进制(不限)
7 布尔:truefalse
8 指针:nullptr
9 指定字面值类型:字符型+前缀u,U,L,u8;整形加后缀u,l,ll,浮点型加后缀f,l

注:理解没有存入变量的数值,是以什么方式进行存储

(4)类型转换

  • 定义:在c++语言中,某些类型之间有关联,当程序需要一种类型运算对象时,可以用另一种类型替换,类型转换分显示、隐式转换两种。
  • 显示转换:手动控制强制转换,(转换后的类型)+ 当前数据,如 (int)3.14
  • 隐式转换:编译器依据C标准自动转换

会遇到隐式转换的情况如下:

  • 算术运算操作数类型不同
  • 赋值操作左右类型不匹配
  • 函数参数传递实参与形参类型不一致
  • 条件表达式非布尔类型出现在条件判断
  • return语句返回值类型与函数声明不符

隐式转换依据的原则:

转换优先级(由低到高):
Bool → char → short → int → unsigned → long → unsigned long →
long long → unsigned long long → float → double → long double

  • 整数提升:char、short等小整型先提升为intunsigned int
  • 赋值转换:右值转为左值类型
  • 函数调用转换:传递实参类型强制变为形参类型,return返回类型强制转为函数声明类型
  • 算术转换:1.无符号类型等级 ≥ 有符号类型 → 有符号转为无符号
  • ---------------2.有符号类型等级 > 无符号类型 → 无符号转为有符号
实际案例:	
1 无符号类型等级 ≥ 有符号类型,unsigned int >= int
int a = -10;
unsigned int b = 20;
int result = a + b;  // 实际计算值:4294967286(32位系统)

所以a被转为了unsigned int,负数强转无符号,结果异常。

2 有符号类型等级 > 无符号类型 long > unsigned int
long x = -5;          // 8字节(64位)
unsigned int y = 10;  // 4字节
long sum = x + y;     // sum = -5 + 10 = 5(无符号转换未发生)

所以y被转为了long

(5)类型选择优化

特点 采取
数据不可能为负时 可以考虑unsigned型
整数的值较大时 int 和long都是32位(win64下),若数据超过范围,直接选取longlong。因为long在跨平台时可能出现丢失精度(linux-windwos),可移植性不佳。
算数表达式中尽量避免char或bool 因为不知道编译器中char是否有符号,容易出现很难发现的问题,明确使用signedchar或unsigned char。(char包含signedchar和unsignedchar,至于char是否有符号由编译器决定,不同编译器可能不一样)
浮点数选用double 和float代价相差不大且精度更高,至于longdouble的精度一般不需要,且有消耗。

3 输入输出

(1) 输入

  • 格式控制严格,若加入多余字符,输入是也要加入同样的字符
  • 连续数值,以空格和分隔符隔开
  • 连续字符则不需要
  • 输入,以 空格,回车,tab,非法字符结束(不属于该数值的字符)
  • putchat() 输单个字符
  • getchar() 输入一个字符(键盘输入时,并非按下立刻输入,而是缓存,按下回车后发送,回车也会被吸收)
    一 C++基础_第3张图片

(2) 输出
printf格式控制操作:

符号 作用
\t tab,间隔
%d d前加数字,域宽,右对齐 (同%i)
%c c前加数字,域宽,右对齐
%f 不指定实数长度,一般是6位有效数字【非小数点后,是全部】(双精度15位)
%m.nf 域宽+小数位四舍五入
%-m.n 左对齐
%m.ne 指数形式,前为小数部分,后为指数。(域宽,保留小数位),E也可以。有些系统输出数列共13列
%o 八进制输出,二进制值3位一组,(注:负数以补码形式输出,不含符号)
%x 十六进制输出,%lmx可输出长整形,指定位数
%u 无符号
%g 输出浮点数,系统自动选择f 和e ,不输出无意义的0
2个百分号 输出:%

(3)多组输入

  • 读取不定输入:int a=0;while(cin>>a)当while检测istream对象,效果是检测流的状态
  • 即是否是有效输入,而不是检测是否非0,此处检测是否为int型数据,若遇到小数,字母,等异常输入都会判定结束,回车不会。

注意:ctrl+z是:windows输入结束判定, ctrl+d是unix


4 变量作用域&生命周期

(1) 变量分类角度

数据除了依据数据类型分类,还可依据作用域生命周期分类。

(2) 作用域划分

  • 【全局变量/外部变量】:定义在main函数外部(全局变量使用extern可在其它文件继承,加static则限制在本文件内使用)
  • 【局部变量】:函数内部的变量,(又称自动变量)

全局变量在程序运行过程中一直存在,占用存储单元,降低函数的通用性

(3) 生存周期划分

  • 【动态存储】函数的形参,函数内动态局部变量(加static会放于静态区)
  • 【静态存储】全局变量,或加statc修饰的局部变量

静态存储 是在编译阶段所完成,程序运行过程中一直存在。一而动态存储则是当执行本函数时,才会赋予初值,分配内存,函数执行完毕后销毁。

(4)函数

  • 【内部函数】若加static修饰,则只能本文件调用
  • 【外部函数】使用extern修饰为外部函数, 但定义函数可若省略,默认为外部函数。调用外部函数时,可在文件前添加:声明函数原型,也可省略extern。

5 指针类型

(1) 引用类型

  • 定义:C++中的引用是一种为变量起别名的方式,它提供了对已存在变量的间接访问
  • 引用注意点:
    • 必须初始化,即绑定对象,不能与字面值算术表达式绑定
    • 引用本身不是对象,故不能引用的引用
    • 对引用类型进行的操作(修改),实际上是对其绑定类型所进行的操作
    • 一旦初始化完成,引用类型将和绑定对象一直绑定在一起,无法再重新绑定到另一个对象
    • 引用类型必须和绑定类型 类型一致(注意对const的引用)
实例:

int a = 10;
int &ref = a;  // ref是a的别名
ref = 20;      // 等同于 a = 20

引用:引用相当于变量的别名,对引用的操作等同于直接操作原变量:


(2) 指针类型

  • 定义:每个变量在内存中都有唯一的地址(如 0x7ffd),指针就是专门存储地址的变量
  • 作用:访问存储的地址,访问该地址的变量值
  • 特点:直接操作内存
    一 C++基础_第4张图片

越是引入复杂的描述理解,越容易混淆

(3) 指针基础操作

  • 指针本身就是一个对象,允许赋值拷贝,虽无需再定义时赋初值,但没有初始化也拥有一个不确定的值,建议初始化
  • 引用不是对象没有实际地址,不能定义指向引用的指针
  • 所有指针的类型都要和它所指向的类型一致
  • 访问无效指针将引发错误,后果无法估计
  • 指针可以指向指针
  • 指针可以先后指向几个不同的对象
  • 需要初始化为空时:使用字面值常量:nullptr
使用 介绍
声明方法 int *p ;
赋值方法 p =& a ; //存入a的地址
访问所存的地址 cout<

访问这个地址的变量值 cout<< *p ;

赋值操作:

int a = 10;
int *b = &a; 
cout<<"b存的地址:"<<b<<endl;
cout<<"这个地址的变量值:"<<*b; 

指针之间相互赋值
int a=10,b=5;;
int *p=&a, *p2=&b; 
p=p2;  //p和p2指向同一个对象b

比较操作:

int *p = &a, *p2 =&b;

1 比较p和p2指向地址是否相同,
if (p == p2) 
*相等情形:都是空指针,或都指向一个地址

2 比较各自指向对象 a和b的值
if(*p==*p2)  

注意:特殊的:void * 指针,可以存放任意类型对象的地址,但不能直接操作void*指向的对象,因为不知道是什么类型。一般用于同其他指针比较,作为函数的输入输出等,用于特殊的情形下

(4)指向指针的指针

  • 普通变量用于存储数据,而指针变量用于存储地址(即指针)
  • 而指针变量的地址则由更高级的指针变量来存储。并且由于指针本身就是对象,所以存在指针的指针,即二级指针来存储指针的地址。
实例:

int a=10;
int *p = &a;//p是指向a的指针
int **q = &p;//q是指向p的指针
cout << "p的内容,a的地址:" << p << endl;
cout << "p指向变量的值,就是a的值:" << *p << endl;
cout << "q的内容,p的地址" << q << endl;
cout << "q指向变量的值,即p的内容,也就是a的地址:" << *q << endl;
cout << "q指向变量的值指向变量的值,就是a的值:" << **q << endl;

注意:不需要纠结声明中的类型修饰符的意义,只是声明符的一部分而已


(5)对指针的引用

  • 引用不是对象,没有指向引用的指针,但存在对指针的引用,如下:
实例:

int* p = nullptr;
int *&t = p; //阅读方法:等号左侧从右向左阅读,&t表示t是引用类型
             //声明符剩余的*表示引用的是一个指针,二级指针同理
t = &a;//t代表p,表示指向对象的内容
*t = 0;//解引用r就是解引用p,得到a

cout << "t代表p即a的地址:"<< t <<endl;
cout << "p的内容,a的地址:"<< p <<endl;
cout << "*t,*p,a的值:"<<*t<<*p<<a <<endl;

注意:
1 对指针的引用方式并非&t= * p而是 * &t=p, t 代表 p,即a的地址
2 int * &t = p阅读方法:等号左侧从右向左阅读,&t表示 t是引用类型,声明符剩余的*表示引用的是一个指针。

(6)内存机制

  • 由于指针可以直接访问内存,就会涉及到C/C++的内存机制:栈、堆、全局/静态存储区和常量区
分区 作用 特点 生命周期
存储局部变量、函数参数、函数返回地址等 编译器自动分配和释放,内存大小有限,访问速度快 与函数调用相关
动态分配内存(malloc/new) 手动管理(free/delete),内存空间大但访问速度较慢。 从分配到释放期间有效
全局/静态存储区 存储全局变量、静态变量(static 修饰) 在程序启动时分配,程序结束时释放 整个程序运行期间有效
常量区 存储字符串常量、全局常量(如 const 修饰的全局变量) 只读,不可修改。在程序启动时分配,程序结束时释放 整个程序运行期间
代码区 存储程序的二进制代码(函数体、指令等)。 只读,不可修改。在程序启动时分配,程序结束时释放 整个程序运行期间

C/C++可以使用(malloc/new,free/delete)手动管理内存


6 const限定符

(1) const定义

  • 对变量类型进行修饰,使它的值不能被改变,防止对这个变量进行修改.
  • 使用特点:
    • const对象一旦创建,值就不能改变。
    • const对象必须初始化赋初值
    • 默认const对象只在同一个cpp文件内有效,不同文件的同名const对象等同于,在不同文件中分别定义了独立变量
    • 只可以对const对象执行不改变其内容的操作,如参与算数运算,去初始化另一个对象,拷贝等
    • 多个文件中共享const对象,需要在const对象定义和声明时都添加extern
实例操作:

1 const直接使用:
const int a=19;//必须初始化,之后不能修改

2const的引用,又称:常量引用
const int a=10;
const int &r=a;//正确

3 错误操作:
r=100;//错误,r不可进行修改操作
int &t=a;//错误,t不是对const对象的引用

常量引用赋值:

const int &t = 20;//常量引用可以绑定 字面值,也可以绑定结果为字面值的表达式

int y = 10;
const int & t1 = y;//常量引用可以绑定 非const修饰的对象

double n = 3.2;
const int & t2 = n;//常量引用右侧甚至可以绑定一个可以转换成该类型的对象

//常量引用和非常量引用区别:
int a = 10;
int& r1 = a;//r1是非常量引用,可修改,左右类型必须一致
const int& r2 = a;//r2是常量引用,不可修改,等号右侧限制见上文解说

注意: 允许一个常量引用绑定 非常量的对象字面值,甚至是一般表达式。常量引用限制的只有不可修改,对于引用的对象是不是常量未做限定


(2) 指向常量的指针

  • 指向常量的指针,即指向const对象的指针
  • 指向常量的指针 const int *注意:
    • 不能通过指针改变指向对象的值;但可以改变指向
    • 可以指向常量和非常量对象,但不能指向字面值;
    • 指向常量的指针类型必须与所指对象 类型一致;
    • 常量对象的地址只能使用指向常量的指针存放;
指向const对象的指针,实例操作:

const int a = 10;
const int *p = &a;

*p = 99;//错误,不能通过指针修改常量对象的值
int *p2 = &a;//错误,不能使用普通指针存放const对象的地址

int b = 10;
const int* p3 = &b;//允许指向常量的指针 指向 非const修饰的对象

int c = 100;
p3 = &c; // 允许指向其它的对象

double d = 2.0;
p3 = &d;//错误!!,不能像常量引用那样,指向可以被转换的对象,左右类型必须一致
        //指向常量的指针,不能指向字面值

指针本神就是对象,有指向常量的指针,和常量指针两种类型


(3) 指针常量

  • 常量指针(指针常量),即指针本身就是个const对象
  • 指针常量int* const注意:
    • 必须显式初始化,初始化后不能改变指向
      *不能用字面值和常量初始化,不能使用const赋值;
      *指针常量的类型必须与所指对象类型一致;
      *指针本身的值不变,但可以修改指向对象的值
指针常量`int* const`操作:

int a = 10;
int* const p = &a;
*p= 99;//可以使用指针修改值

int a2 = 2;
p = &a2;//错误,不能改变指向

const int b = 100;
int* const p2 = &b;//错误!,不能用常量初始化,也不能用字面值

double c = 2.0;
int* const p3 = c;//错误!,类型必须一致

(4)顶层const和底层const

  • 顶层const指:本身是常量(本身不可修改)包括:内置类型常量,指针常量
    • 顶层const特点:
    • 1 必须显式初始化,(因为不可修改,所以定义时必须赋初值);
    • 2 可以进行拷贝操作,(用来拷贝赋值不改变本身的值)
    • 3 各种操作重点主打一个本身不能修改
  • 底层const指:指向对象是常量(指向的对象不可修改)包括:指向常量的指针,常量引用
    • 底层const特点:
    • 1 指向/引用的对象不可修改,重点在于对指向对象进行的操作种类的限制
    • 2 底层const可以用常量或者非常量初始化
实际操作:
底层const: 常量引用, 指向常量的指针
顶层const: 指针常量, 内置类型
    
1 顶层const可以拷贝赋值,
//内置类型
const int a = 3;
int b = a;
//指针常量
int* const p = &b;
int c = *p;

//2 顶层可以为底层赋值,因为定义可知,底层可以使用常量和非常量初始化
//略

大佬文章:常量引用,指向常量的指针和常量指针的区别

注意:特殊的,不可使用指针修改,指向常量内容的常量指针:const int *const


7 表达式与运算符

(1)基本概念

  • 定义:表达式是由一个或多个运算对象组成,对表达式求值将得到一个结果。
  • 字面值和变量是最简单的表达式,结果就是字面值和变量的值
  • 把运算符和一个或多个运算对象组合到一起得到更复杂的表达式。
名称 特点
元运算符 一元运算符:作用于一个对象的运算符,如:&(取地址),*(解引用)
二元运算符:作用于两个对象的运算符,如:>,<,=
三元运算符:作用于三个对象的运算符。
左值与右值 c++的表达式要么是右值,要么是左值。简单理解,左值可以位于赋值语句的左侧,右值不能。
c++中一个重要的原则,在需要右值的地方可以用左值替代,但却不能把右值当左值使用。
优先级与结合律 优先级与结合律决定了运算符和运算对象的组合顺序,诸如±*/运算,先计算乘除再算加减。
特别的,括号无视优先级和结合律,括号里面的表达式先计算。
求值顺序 对于没有指定求值顺序的运算符来说,如果表达式修改了一个对象并且在表达式中在此使用了这个对象,会引发错误产生未定义的行为
如: int i =0; i= 99 + i++; 错误
左结合与右结合 简单理解为,从左向右运算为左结合,从右向做运算为右结合
一般的,一元运算符通常为右结合,二元运算符为左结合
赋值运算符除外,赋值运算符是右结合

左值与右值:
(1) = 需要左值对象,计算结果也为左值
(2) & 作用左值对象,但结果返回一个指针,这个指针是右值
(3) *(解引用),[ ] 求值结果为左值
(4) ++ – 作用于左值,所的结果也为左值

(2)算术运算符

  • 特别的,对于整数除法,c++中的·计算结果会舍弃小数部分
  • 另外对于取模运算m%n其最终结果的正负与n的正负无关,只同m有关,m为正结果为+,m为负结果为-。
  • 最后在表达式求值之前,小整数类型的运算对象会被提升为较大的整数类型,所有运算对象最终会转换成同一类型。

(3)逻辑运算符和关系运算符

  • 关系运算符作用于算术和指针类型,逻辑运算符作用于任意能转换成布尔值的类型。
  • 逻辑运算符与关系运算符的返回值都是布尔类型
  • 特别的逻辑运算符规定了求值顺序:都是当且仅当左侧运算对象无法确定表达式结果时才会计算右侧运算对象的值。这种策略被称为短路求值。

(4)赋值运算符

  • 如果左右两侧运算对象类型不一致,则右侧对象将转换成左侧对象的类型。

(5)递增递减运算符

  • 注意++ - -后置于前置的区别,后置递增递减运算优先级较高
  • 但是它返回的值为作用对象的初始值(递增、减前的值),这一点值得注意。

8其它类型

(1) 常量表达式:

  • 值不会改变且在编译过程中就能得到结果的表达式,包括字面值,用常量表式初始化的const对象。

(2) constexpr变量:

  • 声明为constexpr的变量,一定是一个常量,且必须使用常量表式赋值。

(3) 类型别名:

  • typedef和using可以给类型起个别名
实例:
typedef char ch;
ch a = 'c';

*  using法:
using ch = char;

(4) 类型说明符auto:

  • 编译器帮我们分析表达式所属的类型:
int a = 3, b = 4;
auto c = a + b;//

(5) 类型提示符decltype:

  • 从表达式的类型推断出定义变量的类型,编译器分析表达式并得到它的类型,但不实际计算表达式的值
const int a = 10, & b = a;
decltype(a) x = 9;//x是const int 变量
decltype(b) y = x;//y是常量引用,且引用对象为常量

(6) 自定义数据结构struct:

struct fire {
    int a;
    int b;
};
fire c;//最好把类的定义和对象定义分开

自定义类通常放在头文件中,且类名与头文件名一致。另外const,constexpr变量也常放于头文件中


三 数组与函数

本章介绍字符串、数组、与函数

1 String字符串

  • 定义:string类型是标准库的一种可扩展字符串类型,使用需要< string >头文件以及using std::string;声明
  • cin读取strig类型数据时,会忽略开头的空白(空格,制表符,换行)
  • 使用getline(cin,string对象),可以存空格,但不存换行,遇到换行符结束读取,适用于一次读取一整行。
  • 使用getline可以读取为空时,代表一个空字符串,能通过s.empty()测试出
  • s.size()返回的类型为string::size_type,这是一种无符号类型数据,尽量避免使用size与负数进行的比较问题,负数会自动转换为比较大的无符号类型

string字符串操作

初始化:
string s1;//默认为空 
string s1("name");//初始化为name 
string s1(n,'c');//内容为n个连续的c字符 
string s1(s2); //s2为s1赋值 

1 判断长度:s.size()
2 拼接:s1+s2,s1+'\n',s1+"app le"
3 赋值:s1=s2
4 是否为空:s.empty()
5 是否相等:s1==s2,s1!=s2,(大小写敏感)
6 字典序大小比较:s1>=s2.s1<=s2,s1>s2
7 s[n],访问第n个元素

string字符操作

类型说明符auto:编译器帮我们分析表达式所属的类型
 类型提示符decltype:从表达式的类型推断出定义变量的类型,编译器分析表达式并得到它的类型,但不实际计算表达式的值

字符判断操作,ctype头文件部分函数:
isalpha()是否为字母
isdigit()是否为数字
isupper()是否为大写字母
islower()是否为小写字母
tolower()变小写
toupper()变大写

1 范围的访问string每一个元素:
string s1;
forauto c :s1){  //auto类型可以由编译器自动识别s1的类型作为c的类型
    //可以输出字符,判定字符种类
}

2 范围的改变string每一个元素:
string s2;
for (auto &c : s2) {  //循环变量需要定义为引用类型,c实际被依次绑定到s2中的每个元素上
    c=toupper(c);//全变大写,可以对每一个元素进行修改
}

3 只处理部分字符:
访问string对象单个字符有2种方法:下标,迭代器
下标s[]:接收的是string::size_type类型的值,size()返回值一致,可以用decltype来判断size()类型,
(若使用了using namespace std;可以直接用size_t定义i,即:size_t i=0;)
string s3;
for (decltype(s3.size()) i = 0; i < s3.size(); i++) {
    s3[i] = is(lower(s3[i]));//下标从0到最后,全变小写,i可以不为0,但必须大于0
}

注意:
(1) 多个string对象可以相加,string也可以与字符串相加.但多个string和字符字符串相加时,每个“+”必须至少有一侧是string对象,否则变成了字符串相加,是不合法的。
(2) 特殊的,string s1=s2+"apple"+"banana";是合法的,本质是s2+"apple"作为一个string对象与”banana“相加。


2 vector容器

(1)vector容器

定义:

  • 标准库类型vector表示对象的结合,所有对象类型都相同。集合中每个对象有对应索引,用于访问对象。因为vector容纳其它对象,所以它也被称为容器。
  • vector能存放大多数类型的对象作为其元素,内置类型,类类型,甚至组成vector的元素。但是引用除外,引用不是对象。
  • c++提供的大多初始化方式,在大多情况下可以等价,诸如:int a=0; int a{0};int a={0};int a(0);,特别得,如果提供的事初始元素列表,则只能放在花括号中,不能使用圆括号。
  • vector a(n)不加元素时,若为内置类型如int则自动赋值为0,若为string,则每个都是空string对象。
    • 注意{}和()的区别,前者是列表初始化,后者为值初始化。特别的,vector可以使用{n,“val”}初始化,等价(n,“val”)
    • 向vector使用push_back语句时,不能使用 范围for循环
    • 可以用用范围for访问和改变vector元素(方便)
    • vector下标类型同string相同,为size_type类型,非负
    • vector以及string对象的下标运算符,只能访问已存在的元素,不能使用下标运算来添加元素。使用下标的形式去访问一个不存在的元素将引发错误。

操作:

实例化:

vector<int>k;
vector<double>l;
vector<vector<string>>s;

定义,初始化vector对象

T代表任何类型
vector<T> v1;//空
vector<T> v2(v1);
vector<T> v2=v1;
vector<T> v3(n,val);//连续n个val值
vector<T> v4{val1,val2,val3......};//花括号法
vector<T>v5={val1,val2,val3......};

例:
vector<string> s1{"app","ceshi","ababxyxy"};
vector<int> a(10,99);

常用vector操作

当vector对象不容易初始化时,先将vector对象声明为空,在进行其他操作

 v.empty()
 v.size()
 v.push_back(i) 末尾添加值为i的元素
 v[n]   
 v1=v2   拷贝替换
 v1={a,b,c,...} 拷贝替换对应位元素
 ==,!=  判断元素数量以及对应元素值是否同时相等
 >,<,<=,>= 字典序比较

使用前提:头文件与using std::vector;声明。vector是一个类模板,类模板本身不是类或函数,类模板+我们提供的额外信息 才能实例化具体类,提供信息的方式:在模板名字后加一对尖括号<>,括号内放上信息。vector能在运行时高效快速的增加元素,设定其容量大小反而会使其性能变差。


(2)迭代器

  • 定义:除了下标法可以访问string对象或vector元素,存在一种更通用的方式实现目的,这就是迭代器
  • 不同容器的迭代器不能进行比较
  • 迭代器的操作一定程度和指针类似,例如通过解引用获取所指示的元素
  • ->运算符把解引用和成员访问两个操作结合在一起
  • 范围for语句中,不要向容器中添加元素

获取迭代器 与 基本操作:

string s;
auto b=s.begin();  //b指示s的首位元素
auto e=s.end();  //e指示s的尾后元素
if(b!=e){}   //使用==和!=比较两个合法迭代器是否相等
cout<<*b;  //使用*iter来返回迭代器指示的元素,类似指针操作
cout<<*(b++;//使用++--移动迭代器指示位置

vector<string> k;
auto it=k.begin();
if(it->empty()){  //使用it->mem获取对象类的成员或方法,等价于(*it).mem
cout<<null;
}

标准迭代器遍历写法:

string s{ "kower cmp0" };

//C++中尽量只使用==和!=,因为有的有的容器不支持>,<
for (auto it = s.begin(); it != s.end() && (*it); it++) 
	cout << *it << endl;
//若容*it类型为类,则判断非空写法为:&&!it->empty()

string 和 vector 支持的额外运算符操作:

it为迭代器你
it+n  迭代器指示位置向前后移动了n个元素
it-n
it+=n 支持复合赋值语句
it-=n
it1-it2 得到两个迭代器间距离,可负,但二者必须属于同一个容器(没有迭代器加法)
> < = >= <= 支持位置比较(注意:并非所有容器都支持!)

经典方法,二分搜索:

char ans = '7';//待查找
string s{ "123456789" };
auto b = s.begin(), e = s.end();
auto mid = s.begin() + (e - b) / 2;
while (b != e && *mid != ans) {
	cout << "mid:"<< * mid << endl;//查看中值
	if (*mid > ans)
		e = mid ;  //根据有序性调整begin和end
	else
		b = mid+1;
	mid = b+(e-b) / 2;
}

注意:所有的标准库容器都可以使用迭代器进行访问,但只有少数几种才支持下标运算符。迭代器提供了对对象的间接访问,可以访问某个元素,或从一个元素移动到另一个元素,并且我们不必在意迭代器的类型是什么。


3 数组

(1)一维数组

  • 定义:一种复合类型,存放相同类型对象的容器,对象本身没有名字,需要通过位置进行访问,数组大小确定不变,不能向数组中随意增加元素,其性能更好,但损失了灵活性。
  • 特点:
    • 声明时:下标为常量表达式
    • 未赋值元素自动设0,字符为’\0’,指针为‘‘null’
    • 多维数组是线性存储的
    • 字符串常量会自动加一个\0,长度+1(ascii 0字符)
    • 检测\0位置表结束,而非数组长度。遇到\0停止输出。输入

初始化:

char a[4]="appl";//错误!,appl为字符串常量,最后还有一个'\0',维度为4存不下!
char a[5]="appl";//正确!
int a[10]={};//初始化为0

声明读法(从数组名开始,由内向外):
int a[1010];//含有10个整形变量的数组
double b[1010];//含有1010个浮点型变量的数组
char c[1010];
int *ptr[10];//含有10个指针变量的数组

int (*p)[1010]=&a;//p指向含有1010个正数的数组a
double (&r)[1010]=b;//r引用一个含有1010个变量的数组b

字符串函数:

==特别的常用字符串处理函数:(使用字符串处理函数时包含头文件< string.h >,仅对于字符数组管用)==

* strcat(a,b)连在第一个后面
* strcpy(a,b)后复制前【不能用字符串常量】,strncpy(a,b,n)前n个字符复制过去
* strcmp(a.b)字符串字典序比较
* strlen():测量字符串长度,依据\0的位置
* strlwr,strupr,小写,大写

(2) 数组与指针

  • 理解:数组名就是指向数组首元素的指针
  • 即:a~&a[0](auto会判定数组类型为指针,但decltype会判定类型为数组)
实例操作:

数组:int a[10];
1 首元素:int *p = a; p指向首元素 (特别的字符数组:输出a,结果不是地址,是全部内容)
2 下一个:p++,p指向下一个元素
3 尾后指针:int *p2 = &a[10],(!!不能解引用和递增,唯一作用,初始化p2)

(使用库函数: begin(数组名),end(数组名),得到首地址和尾后地址,<iterator>int a[10];
begin(a)为a[0]地址
end(a)为a[10]地址
使用时注意,end(a)不代表输入最后一位数据的地址

指针作迭代器方法:

1 地址遍历:int *p = a,*p2 = &a[10];
for(int *i=a; i!=p2; i++){
}

或:
int i=begin(a),j=i+n; //仅对有数据部分进行遍历;
while(i!=j) {
    i++;   //n为数组元素数
}

(3) 指针运算

  • 必须指向同一个数组,或数组尾元素下一元素。例如int a[10],尾后地址为:&a[10],不是11,因为数组内0~9。
  • 对于超出尾后的部分,是未定义的,会得到未知的结果,并且编译器不会报错
实例操作:

*  指针加减某整数值:指针向前(后)移动了若干位置(不能超出数组范围n)
*  指针之间相减表距离: 结果位ptrdiff_t类型,带符号,<cstddef>(同size_t)
*  使用运算符进行比较:>,<,==,!=(不能用<=,<=)
*  解运用:int a[10], a为指针, a[i]等价于*(a+i)
解引用:
int a[10];
int ans=*a;//等价:ans=a[0]
int ans2=*(a+4); //等价:ans2=a[4]
int ans3=*a+4;//ans3= a[0]+4
下标解释:
int k=a[5];//K=*(a+5),a为首元素地址
int *P=a;//p指向a[0],a就是a[0]的地址
k=p[1];//k=*(p+1),解引用 p前后移动:*(p+num)或:p[num]
k=p[-1]//k=*(p-1),对于标准库类型的下标要求无符号类型,但内置类型无此要求,但要在数组维度内

4 多维数组

(1)基本定义

  • 定义:是数组的数组,定义数组时对下标的数量没有限制,习惯于把第一个维度称为行,第二个维度称为列
  • 初始化:双层花括号,单层花括号(没有显示定义但在维度范围内的值会被初始化为0(除了char型)
  • 下标引用维度和多维数组维度相同,代表引用具体元素;下标引用小于数组维度,则结果为内层数组(如:指向数组的指针或对数组的引用)
  • 范围for:改变元素时,需定义引用类型
  • 范围for:不写任何操作时,除最内层循环外,其余层控制变量仍然要求为引用类型(数组名会被编译器转化为指向首元素的指针)
实例操作:

//初始化,全0
int a[10][10] = {};

//范围for改变数组元素,必须全部使用引用
int k = 0;
for (auto& r2 : a)
	for (auto &c2 : r2) 
		c2 = k++;

//范围for查看元素,除最内层循环,其余层必须使用引用
for (auto& row : a) {
	for (auto col : row)
		cout << col;
	cout << endl;
}

(2)指针与多维数组

  • 理解:多维数组是数组的数组,多维数组名,实际代表指向第一个内层数组的指针

  • 指向数组的指针:int a[10][10]={}; int (*p)[10]=a;

    • 首先,p是一个指针,意义为p是指向10个整数数组的指针,括号不可省略
    • 其中:p,*p,a,*a具体代表为a[0][0]的地址,由于p是指向数组的指针,所以一次解引用得不到具体值,这一点不同于指向具体元素的指针
    • 其次:指向数组的指针不能用普通指针表示,必须用诸如 int (*p)[10]=a;的形式
    • 最后:解引用得到具体值: **p~a[0][0], * (*p+1) ~ a[0][1]......,即: *(*p+k)~a[0][k]
实例操作:
//初始化
	int a[5][5] = { {0,0,0,0,0}, {1,1,1,1,1},{2,2,2,2,2},{3,3,3,3,3,},{4,4,4,4,4,}};
	
	//指针遍历输出二维数组,使用auto(或decltype)快捷形式
	//至少,不用再 int (*p)[5]=a这么麻烦了
	for (auto p = a; p < a + 5; p++) {  //p指向含5个整数的数组
		for (auto q = *p; q < *p + 5; q++)//q指向5个整数数组的首元素
			cout << *q;
		cout << endl;
	}
	//也可以使用begin和end函数,使用end函数要确保多维数组中元素装的满满的

拓展:若int (*p)[10]=a+m;则:*(*p+k)~a[m][k](指针运算)


5 函 数

(1)基础

  • return可以不止一个,可是是表达式
  • 返回值类型,与函数类型一致,否则自动转化
  • 函数声明,能快速调用函数(实际形参名可以省略)
  • 所有函数前的声明:外部声明,对整个文件有效

(2)构成

  • 形参,不调用,不占用内存,调用时才临时分配
  • 实参:可以使用常量,变量,表达式
  • 过程:本质是单向的值传递
    • 实际参数把 传递给形参,return把值返回
    • 调用结束,形参内存被释放

(3)数组作形参

  • 本质:调用时才分配连续的内存单元,仍为值传递
  • 形参:数组名:
    • 传递首元素地址,函数定义时,也可以不写范围如add(int a[])add(int a[10])
    • 主调处和被调处,分别定义数组
    • 由于传递地址,所以数组值会被修改,共享一块内存。除非加const限定,或者传数组的副本
    • 二维数组传递,必须定义清楚基础维,系统不检查前几维大小:int a[ ] [10]不能int a[10] [ ]

C语言不检测形参大小,只是调用时传个地址过来,所以不设值也可以


四 结构化模块

标准模块化开发

文件结构

(1) 文件结构

  • C语言中设计结构化模块并合理设置文件结构是提升代码可维护性、可扩展性和协作效率的关键
  • 模块化设计原则:
    • 单一职责:每个模块只解决一类特定问题
    • 高内聚低耦合:模块内部紧密相关,模块间依赖小
    • 接口暴露最小化:头文件仅公开必要接口,隐藏实现细节
    • 分层架构:按抽象层次划分模块(硬件抽象层/HAL等)
标准文件结构:

project_root/
├── docs/            # 设计文档
├── include/         # 公共头文件(*.h)
├── src/             # 源文件(*.c)
│   ├── module_a/    # 子模块
│   ├── module_b/
│   └── main.c       # 程序入口
├── lib/             # 第三方库
├── tests/           # 单元测试
├── build/           # 编译输出
├── Makefile         # 构建脚本
└── README.md        # 项目说明

(2)头文件

  • 禁止在头文件定义变量(extern声明除外)
  • 内联函数需加static inline限定
  • 平台相关代码使用宏隔离
头文件模板:

// module_math.h
#ifndef MODULE_MATH_H   //1 头文件保护宏
#define MODULE_MATH_H

#include      //2 只包含必要头文件

#ifdef __cplusplus      //3 C++兼容
extern "C" {
#endif

#ifdef LINUX           //平台隔离
void linux_specific_func();
#endif

#define PI 3.1415926   //4 宏定义
typedef struct {      //类型声明
    double x;
    double y;
} Point;

//5 函数接口
double calculate_distance(const Point* p1, const Point* p2); 

#ifdef __cplusplus
}
#endif

#endif

尖括号包含:<>从编译系统的子目录里找,称为标准方式
双撇号包含“”:存放云程序的子目录中寻找,找不到再按标准方式找。或用句对路径“C\temp…”

(4)源文件模板

  • 全局变量使用static限制作用域
  • 复杂函数添加文档注释

源文件模板:

// module_math.c
#include "module_math.h"
#include       // 本模块私有头文件

// 静态函数(仅本文件可见)
static double square(double x) {
    return x * x;
}

// 接口实现
double calculate_distance(const Point* p1, const Point* p2) {
    return sqrt(square(p1->x - p2->x) + square(p1->y - p2->y));
}

你可能感兴趣的:(一 C++基础)