基本内容介绍
(1) 程序
一组计算机能识别的指令,命令。执行对应的操作。没有指令,计算机不执行任何操作。
(2) 语言
人要操控计算机,需要一种二者都能直接识别的话,经历了不同阶段发展
类型 | 介绍 |
---|---|
机器语言 | 计算机能直接识别的01二进制码,这些二进制码的集合,就叫机器语言 |
符号/汇编语言 | 人把一些操作,简化为符号表达出来,简单好记一些 |
高级语言 | 接近人习惯使用的自然语言和数学语言,容易理解 |
(3) 编译
把高级语言写的程序,翻译
为机器指令,然后计算机便可以执行。这一过常称为编译,然后计算机便可以根据指令,得到结果。
注意:值得一提.obj:是汇编程序
(1) 定义
一种通用的、高效的编程语言,具有简洁的语法和强大的底层控制能力
特点 | 介绍 |
---|---|
简洁 | 紧凑灵活,仅必要内核。输入输出与文件操作,内存管理都由编译系统(GCC)提供的库函数实现 |
结构化编程 | 语法限制不严,设计自由度高 |
以函数为基本单位 | 程序几乎所有部分都由函数完成,函数是C的基本单位,C就是编写一个个函数 |
模块化设计 | 以程序文件为对象进行编译,一个cpp是一个编译单位, |
指针系统 | 直接操作内存地址,实现高效数据结构和内存管理 |
应用:操作系统开发(Linux内核、Windows驱动),嵌入式系统(物联网设备、微控制器),高性能计算(算法核心、游戏引擎)
(2) 程序运行过程
编辑
程序(C编写代码)——>编译
源程序(高级语言翻译为机器指令)——>源程序变为二进制目标程序(机器指令程序.obj)——>链接
生成可执行程序(编译得到的机器指令模块,与函数库相连)
程序设计过程:问题分析——>设计算法(解题方法于步骤)——>编辑程序,编译,改错
(1) 定义
在C程序设计中,算法是解决问题的明确步骤集合
,是程序设计的核心,决定了程序的逻辑结构、效率与可靠性。(逻辑,思路,解决问题的方案)
(2) 设计工具
文章:算法基础整合
算法已经与人类知的识体系深度结合
(1) 定义
数据结构:是计算机存储、组织数据
的特定方式
,旨在实现高效访问与修改操作。其本质是现实问题到计算机模型的抽象映射
(2) 分类
依逻辑结构可简单分类:
类型 | 典型数据结构 |
---|---|
线性结构 | 数组、链表、栈、队列、哈希表 |
树形结构 | 二叉树、堆、B树、Trie树、红黑树 |
图形结构 | 邻接矩阵、邻接表、十字链表 |
集合结构 | 集合(Set)、位图(Bitmap) |
注意:算法与数据结构相互依存,算法必须通过数据结构操作数据
本章介绍:变量与基本类型,数据细节,内存与指针,输入与输出,表达式与运算符
(1) 定义
存储空间
,用于存储数据。数据的类型决定了存储空间大小和布局方式。(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就会出现错误
(1) 正数与负数的存储
补码
存储的。也可以说,所有二进制数在计算机中都用补码来进行存取
注意:
有符号整数进行加减运算,实际上是采用数据的补码进行加法运算,得出的结果为补码形式,
即:正数补码就是原码,负数减1再取反可得负数原码。
(2)一字节大小数据的数值范围
注:掌握细节,了解边界情况
(3) 字面值常量
35
这种没有名字直接用他们的值来称呼的常量,统称为字面值常量
其常见数据类型为:
1 十进制:最小匹配int,long,longlong,(默认带符号)
2 八进制十六进制:最小匹配int,u~int,long,u-long,longlong,u-longlong
3 浮点型:字面值为double,表现形式:3. , .3 , 0.3e1(科学计数法,0.3*10)
4 字符型:char
5 字符串:arry,末尾加’\0‘,故实际长度+1
6 转义字符:不可打印,特殊含义,用‘\’+字母,或‘\’+八进制(最多3个)或‘\x’+16进制(不限)
7 布尔:true,false
8 指针:nullptr
9 指定字面值类型:字符型+前缀u,U,L,u8;整形加后缀u,l,ll,浮点型加后缀f,l
注:理解没有存入变量的数值,是以什么方式进行存储
(4)类型转换
(转换后的类型)+ 当前数据
,如 (int)3.14
会遇到隐式转换的情况如下:
隐式转换依据的原则:
转换优先级(由低到高):
Bool → char → short → int → unsigned → long → unsigned long →
long long → unsigned long long → float → double → long double
char、short
等小整型先提升为int
或unsigned int
实际案例:
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的精度一般不需要,且有消耗。 |
(1) 输入
(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对象,效果是检测流的状态注意:
ctrl+z
是:windows输入结束判定,ctrl+d
是unix
(1) 变量分类角度
数据除了依据数据类型分类,还可依据作用域
和生命周期
分类。
(2) 作用域划分
全局变量在程序运行过程中一直存在,占用存储单元,降低函数的通用性
(3) 生存周期划分
静态存储 是在编译阶段所完成,程序运行过程中一直存在。一而动态存储则是当执行本函数时,才会赋予初值,分配内存,函数执行完毕后销毁。
(4)函数
(1) 引用类型
实例:
int a = 10;
int &ref = a; // ref是a的别名
ref = 20; // 等同于 a = 20
引用:引用相当于变量的别名,对引用的操作等同于直接操作原变量:
(2) 指针类型
越是引入复杂的描述理解,越容易混淆
(3) 指针基础操作
使用 | 介绍 |
---|---|
声明方法 | 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的地址。
2int * &t = p
阅读方法:等号左侧从右向左阅读,&t表示 t是引用类型,声明符剩余的*表示引用的是一个指针。
(6)内存机制
分区 | 作用 | 特点 | 生命周期 |
---|---|---|---|
栈 | 存储局部变量、函数参数、函数返回地址等 | 编译器自动分配和释放,内存大小有限,访问速度快 | 与函数调用相关 |
堆 | 动态分配内存(malloc/new) | 手动管理(free/delete),内存空间大但访问速度较慢。 | 从分配到释放期间有效 |
全局/静态存储区 | 存储全局变量、静态变量(static 修饰) | 在程序启动时分配,程序结束时释放 | 整个程序运行期间有效 |
常量区 | 存储字符串常量、全局常量(如 const 修饰的全局变量) | 只读,不可修改。在程序启动时分配,程序结束时释放 | 整个程序运行期间 |
代码区 | 存储程序的二进制代码(函数体、指令等)。 | 只读,不可修改。在程序启动时分配,程序结束时释放 | 整个程序运行期间 |
C/C++可以使用(malloc/new,free/delete)手动管理内存
(1) const定义
不能被改变
,防止对这个变量进行修改.实例操作:
1 const直接使用:
const int a=19;//必须初始化,之后不能修改
2 对const的引用,又称:常量引用
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 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) 指针常量
int* 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 顶层const可以拷贝赋值,
//内置类型
const int a = 3;
int b = a;
//指针常量
int* const p = &b;
int c = *p;
//2 顶层可以为底层赋值,因为定义可知,底层可以使用常量和非常量初始化
//略
大佬文章:常量引用,指向常量的指针和常量指针的区别
注意:特殊的,不可使用指针修改,指向常量内容的常量指针:
const int *const
(1)基本概念
名称 | 特点 |
---|---|
元运算符 | 一元运算符:作用于一个对象的运算符,如:&(取地址),*(解引用) |
二元运算符:作用于两个对象的运算符,如:>,<,= | |
三元运算符:作用于三个对象的运算符。 | |
左值与右值 | c++的表达式要么是右值,要么是左值。简单理解,左值可以位于赋值语句的左侧,右值不能。 |
c++中一个重要的原则,在需要右值的地方可以用左值替代,但却不能把右值当左值使用。 | |
优先级与结合律 | 优先级与结合律决定了运算符和运算对象的组合顺序,诸如±*/运算,先计算乘除再算加减。 |
特别的,括号无视优先级和结合律,括号里面的表达式先计算。 | |
求值顺序 | 对于没有指定求值顺序的运算符来说,如果表达式修改了一个对象并且在表达式中在此使用了这个对象,会引发错误产生未定义的行为 |
如: int i =0; i= 99 + i++; 错误 | |
左结合与右结合 | 简单理解为,从左向右运算为左结合,从右向做运算为右结合 |
一般的,一元运算符通常为右结合,二元运算符为左结合 | |
赋值运算符除外,赋值运算符是右结合 |
左值与右值:
(1) = 需要左值对象,计算结果也为左值
(2) & 作用左值对象,但结果返回一个指针,这个指针是右值
(3) *(解引用),[ ] 求值结果为左值
(4) ++ – 作用于左值,所的结果也为左值
(2)算术运算符
m%n
,其最终结果的正负与n的正负无关,只同m有关,m为正结果为+,m为负结果为-。(3)逻辑运算符和关系运算符
无法确定
表达式结果时才会计算右侧运算对象的值。这种策略被称为短路求值。(4)赋值运算符
(5)递增递减运算符
后置
递增递减运算优先级较高
(1) 常量表达式:
(2) constexpr变量:
(3) 类型别名:
实例:
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变量也常放于头文件中
本章介绍字符串、数组、与函数
using std::string;
声明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;
for(auto 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“相加。
(1)vector容器
定义:
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<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,...} 拷贝替换对应位元素
==,!= 判断元素数量以及对应元素值是否同时相等
>,<,<=,>= 字典序比较
使用前提:
。vector是一个类模板,类模板本身不是类或函数,类模板+我们提供的额外信息 才能实例化具体类,提供信息的方式:在模板名字后加一对尖括号<>,括号内放上信息。vector能在运行时高效快速的增加元素,设定其容量大小反而会使其性能变差。
头文件与using std::vector;声明
(2)迭代器
获取迭代器 与 基本操作:
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;
}
注意:所有的标准库容器都可以使用迭代器进行访问,但只有少数几种才支持下标运算符。迭代器提供了对对象的间接访问,可以访问某个元素,或从一个元素移动到另一个元素,并且我们不必在意迭代器的类型是什么。
(1)一维数组
初始化:
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) 数组与指针
实例操作:
数组: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) 指针运算
实例操作:
* 指针加减某整数值:指针向前(后)移动了若干位置(不能超出数组范围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),对于标准库类型的下标要求无符号类型,但内置类型无此要求,但要在数组维度内
(1)基本定义
实例操作:
//初始化,全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,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]
(指针运算)
(1)基础
(2)构成
(3)数组作形参
add(int a[])
或 add(int a[10])
基础维
,系统不检查前几维大小:int a[ ] [10]不能int a[10] [ ]C语言不检测形参大小,只是调用时传个地址过来,所以不设值也可以
标准模块化开发
(1) 文件结构
标准文件结构:
project_root/
├── docs/ # 设计文档
├── include/ # 公共头文件(*.h)
├── src/ # 源文件(*.c)
│ ├── module_a/ # 子模块
│ ├── module_b/
│ └── main.c # 程序入口
├── lib/ # 第三方库
├── tests/ # 单元测试
├── build/ # 编译输出
├── Makefile # 构建脚本
└── README.md # 项目说明
(2)头文件
头文件模板:
// 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)源文件模板
源文件模板:
// 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));
}