秋程设
ZJU秋程设学习笔记
补充知识:二进制,存储
位(bit)、字节(Byte,B)
位(bit):
简单来说,一位 就是 一个二进制数
官方一点:数据存储的最小单位。在计算机中的二进制数系统中,位,简记为b,也称为比特,每个0或1就是一个位(bit)。计算机中的CPU位数指的是CPU一次能处理的最大位数。例如32位计算机的CPU一个机器周期内可以处理32位二进制数据的计算机。
字节:简单来说就是8个二进制数,即8 bit 就称为一个字节(Byte)
字节这个词最早起源于1956年前后,由IBM公司提出。最早的拼写方式是bite,但是为了避免与bit混淆用y代替了i。到20世纪60年代中叶,在IBM的360系统的方展下(一种大规模复杂的商用计算机)字节这个词逐渐开始用来表示一组8比特数据。
字:自然的存储单位
在计算机中,一串数码作为一个整体来处理或运算的,称为一个计算机字,简称字。字通常分为若干个字节(每个字节一般是8位)。在存储器中,通常每个单元存储一个字,因此每个字都是可以寻址的。字的长度用位数来表示。
在计算机的运算器、控制器中,通常都是以字为单位进行传送的。字出现在不同的地址其含义是不相同。例如,送往控制器去的字是指令,而送往运算器去的字就是一个数。
整数的二进制存储
以16位有符号二进制为例:
范围:-32768~32767
-1的二进制补码为:1111 1111 1111 1111
反码:原码取反。补码:反码+1
正数的补码为本身,负数的补码不为本身
整型在计算机里都是以反码的形式存储的
浮点数的二进制存储(IEEE754标准)
参考:https://blog.csdn.net/freeristantent/article/details/124066890
以64位双精度浮点数为例:
第1位:符号位
第2-12位:阶码位 共11个
第13-64位:尾数位 共52个
类型划分
11位的指数部分可存储00000000000 ~ 11111111111(十进制范围为0 ~ 2047),取值可分为3种情况:
- 11位指数不为00000000000和11111111111,即在00000000001 ~ 11111111110(1 ~ 2046)范围,这被称为规格化。
- 指数值为00000000000(0),这被称为非规格化
- 指数值为11111111111(2047),这是特殊值,有两种情况:
- 当52位小数部分f全为0时,若符号位是0,则表示+Infinity(正无穷),若符号位是1,则表示-Infinity(负无穷)
- 当52位小数部分f不全为0时,表示NaN(Not a Number)
规格化
规格化下,浮点数的形式可表示为:
s为0或1,0表示正数,1表示负数,对应1bit的符号位
f为52位有效位,其中的每一位b是0或1,对应52bit的小数部分(不足52位补0)
c是超出52位的部分(如果有的话,需要舍入(精度会丢失),规则下述)
e为十进制数值,其11位的二进制数值对应11bit的指数部分
1023为移码,移码值为
这里的\(n\)表示指数位数,对于64bit的双精度存储,n是11
Chapter 1 类型、运算、表达式
数据类型:
C语言的3种基本数据类型是整型、字符型和浮点型:
- 整型
int
取值范围:-2147483648~+2147483647 - 短整型
short
取值范围:-32768~+32767 - 长整型
long
取值范围:-2147483648~+2147483647 - 字符型
char
取值范围:-128~+127 - 单精度浮点型
float
取值范围:±3.4e38 - 双精度浮点型
double
取值范围:±1.7e308 - 无符号型:
unsigned
注:在没有特殊需要的情况下,整型变量优先使用int,浮点型优先使用double,操作变量时,需要非常注意数据是否会溢出!
标识符的命名规则:
- 必须有字母、数字(不能是第一个字符)、_(被看作字母)构成
- 不能和保留的关键字(keyword)重复,但是可以和库函数名重复,比如int这个命名是非法的,scanf这个命名是合法的。
字符常量
即'A',和字符串有本质区别,其本质为整数(ASCII码)(char:0-127,一字节整数)
- 字符常量的表示方法:
- 'A'
- '\n' 转义字符
- '\ooo' '\xhh' 算出来等于ASCII码就行了。
注:不加x的数字默认为八进制
char c = '\101'
printf("%c",c);
结果为A
运算符以及优先级
- 算数运算符
+、-、*、/、%
类型大致一致才能进行运算,整数的除法会自动取整。
可以强制类型转换,但是有可能损失精度! - 关系运算符
>,>=,<,<=/==,!=
- 逻辑运算符
&& || (!为单目)
需要注意的是逻辑运算符在能确定结果时就不会往下计算,是个懒鬼。 - 三目运算符
X?A:B
与if-else等价 - 单目运算符
- +,-
- !
- i++,++i,i--,--i:i++表示先取i原来的值,然后i本身增加1.++i表示i先增加1,然后取i的值.(也就是取了原来i+1的值)
不管++在前在后,i的值一定会改变
此外,++/--运算符只能对变量使用,不能对表达式使用
举个例子:
设 i = 1.
a = c[i++]; 执行后,a = c[1],i = 2。
a = c[++i]; 执行后,a = c[2],i = 2.
- 逗号运算符 优先级最低 (a,b,c)-->值为c
- 我也不知道这东西发明出来有什么用
- (补充)按位运算符:
- & 按位与
- & 是个双目运算符,双目是指参与运算的对应二进制位,它的逻辑也比较容易理解:对应二进制位均为1时值才为1,否则为0,但是要注意的是,参与运算的数都以补码的形式存在,大概介绍一下补码:正数的补码就是其本身,负数的补码在原码的基础上符号位不变,其余位取反。
- & 按位与
3 : 00000011//3的2进制补码
4 : 00000100//4的2进制补码
00000000// 3&4结果为0的2进制补码
- | 按位或
- | 也是个双目操作符,它的逻辑很简单:运算中对应的位中有 1 那么结果就是 1
1 : 00000001
3 : 00000011
00000011//结果为3
- ^ 按位异或
- ^ 是个双目操作符,逻辑也比较清晰:对应的位相同结果为 0,不同结果就为 1
1 : 00000001
3 : 00000011
00000010//结果为2
运算特性:
int main()
{
int n = 1;
int m = 2;
n ^= m;
m ^= n;
n ^= m;
printf("%d,%d",n,m);
return 0;
}
我们惊奇地发现 n 与 m的值完成了互换,这是为什么呢?
一个数与自身进行按位异或运算,其结果必为0,一个任意数与0进行异或运算,结果为其本身。
- ~ 按位取反
- 符如其名,~ 的逻辑是:对应的位 1 变 0,0 变 1
- 后面会学的:数组下标运算符[],返回所占字节运算符sizeof(),强制转换类型运算符(),指针运算符*/&
运算符优先级
单目>双目、
&&>||、
按位>算数>关系>逻辑
注:先进行位运算才能进行算术运算,算术运算与四则运算吻合。算完算数才能进行关系比较产生真假值,产生真假值才能进行逻辑判断。因此这个运算优先级顺序是很自然的。
输入、输出函数
- scanf()
- getchar()//读取一个字符
- printf() %d(有符号十进制),%ld,%f,%lf,%u(无符号型),%c,%s(字符串)
- putchar()//输出一个字符
常用数学函数
#include <math.h>
平方根函数sqrt(x):计算\(\sqrt x\)。返回值为double
绝对值函数fabs(x):计算|x|.
幂函数pow(x,n):计算\(x^n\)。
指数函数exp(x):计算\(e^x\)。
以e为底的对数函数\(log(x)\)。
根据老师的讲解,由于函数pow(x,n)内部是以泰勒展开计算,效率比较低
Chapter 2 分支、顺序结构
单分支结构:if - else
多分支结构:else if/switch-case
说明:
- else会和最近的没有被else配对的if配对,不论缩进。
- case 后的值必须是常量(不能是表达式啊)而且不能重复
注:1. 单个switch语句就能实现多分支结构,但如果只有一个if else,最多只能实现二分支结构,必须多个else if语句配合;
2. switch语句需要配合break语句实现多分支结构,否则有可能出现多个分支都被执行的情况,合理运用这个特点可以作为一种技巧,而else-if的判断则是非此即彼的。
Chapter 3 循环结构
三种循环:for循环、while循环、do-while循环。
- for循环:常用于循环次数确定,易用步长度量
- while循环:适用各种情况,还有万能while(1)写法
- do-while循环:总会执行一次
常见应用:
- for循环常见于:固定项数数列求和,数组的读入等
- while循环常见于:字符文本读入,精度数列求和。
break,跳出循环。continue,开始新一轮循环。goto,跳转到指定位置。
Chapter 4 函数
函数调用过程
任何C程序执行,首先从主函数main()开始,如果遇到某个函数调用,主函数将被暂停执行,转而执行相应的函数,该函数执行完后将返回主函数,然后再从原先暂停的位置继续执行。
可以用debug功能去体会这个流程.
返回值
函数结果返回的形式如下:
return 表达式;
先求解表达式的值,再返回其值。一般情况下表达式的类型与函数类型应一致,如果两者不一致,以函数类型为准。return语句的作用有两个:一是结束函数的运行;二是带着运算结果(表达式的值)返回主调函数。
在函数体中,return语句中的表达式反映了函数运算的结果,通过return语句将该结果回送给主调函数。但return语句只能返回一个值,如果函数产生了多个运算结果,将无法通过return返回。例如求一元二次方程的函数,就不能用return返回两个根。
在接下来的学习中,我们将会学习使用全局变量或指针实现函数多个结果返回。
- return语句只能返回一个值。
- 利用指针可以带回多个值”
void——不返回结果的函数
前面我们谈的的函数主要是是起计算或判断作用,最终有一个函数结果返回。在很多程序设计中,调用函数不是为了得到某个运算结果,而是要让它产生某些作用,具有类似作用的函数在有些语言中也称为过程。
没有返回值的函数:
void Name(parameter list)
{
body
}
函数类型为void,表示不返回结果。void类型的函数虽然不直接返回一个值,但它的作用通常以屏幕输出等方式体现。
注:如果给在函数体中写return语句的话,会得到warning而非error.
在不返回结果的函数定义中,void不能省略;否则,函数类型被默认定义为int。
对于void类型的函数,如果省略了return语句,当函数中所有语句都执行完后,遇到最后的大括号即自动返回主调函数。
由于函数没有返回结果,函数调用不可能出现在表达式中,通常以独立的调用语句。
不返回结果的函数在定义、调用、参数传递、函数声明上,思路完全与以前相同,只是函数类型变为void。它适用的场合主要是把一些确定的、相对独立的程序功能封装成函数。 主函数通过调用不同的函数,体现算法步骤,而各步骤的实现由相应函数完成,从而简化主函数结构,以体现结构化程序设计思想。
注:如果f()是一个void类型的函数,那么如:
a = f();
类的语句是会报错的!
形参与实参:
- 形参(parameter):函数原型,函数声明中的参数,只能为变量
- 实参(argument):最开始接触的那种参数,变量常量表达式都可以
PTA概念错题 - 形参和实参的命名可以重复,但是他们占用的内存不一样。
- 在C语言中,可以将main放在自定义的后面,省略函数的声明。
- 在C语言的函数定义中,如果不需要返回结果,就可以省略return语句
- 在C语言的函数定义中,如果省略了return语句,函数也会返回主调函数。因为函数结束后会自动回到调用函数结束的位置
- 参数的传递只能由实参传给形参
有关变量的一些认识
- 变量可以分为:静态/自动/寄存器变量,或者局部变量(Local)和全局变量(Global)
- 全局变量默认静态,可以用static 来创建一个静态的本地变量。
- 静态变量默认赋值0,生存周期是整个程序运行周期.
编译预处理
#define
宏替换,整体的替换
关于宏的其他应用,可以看以下文章
https://zhuanlan.zhihu.com/p/447763456
https://blog.csdn.net/weixin_58165485/article/details/124146570#include
/"include"
- 编译预处理不占用运行时间
- 利用编译预处理可以实现类似于函数的功能。
Chapter 5 数组
- 一维数组基本排序和插入算法.(见"常见算法代码示例")
- 二维数组的地址计算:a[i][j]的地址 =
X+i*sizeof(a[0])+j*sizeof(a[i][j])
- 字符串:末尾有\0,了解一些字符串处理函数(见附录)
- 多个字符串可用二维数组接收方便管理
PTA错题
- 将二维数组的行下标和列下标分别作为循环变量,通过二重循环,就可以遍历二维数组,即访问二维数组的所有元素。由于二维数组的元素在内存中按行优先方式存放,将行下标作为外循环的循环变量,列下标作为内循环的循环变量,可以提高程序的执行效率。
- 在C语言中,第一维下标可以通过初始化默认,数组的第二维下标不能默认。
- 字符串数组赋值
例题:选项( [ABCD])与以下字符数组定义等价。
static char s[6] = {'H', 'a', 'p', 'p', 'y', '\0'};
A.static char s[6] = {'H', 'a', 'p', 'p', 'y'};
B.static char s[6] ="Happy";
C.static char s[6] ={"Happy"};
D.static char s[6] = {'H', 'a', 'p', 'p', 'y', 0};
//注意static.
Chapter 6 指针
注意,指针变量的类型不是指指针变量本身的类型,而是指它所指向的变量的数据类型。指针变量自身所占的内存空间大小和它所指向的变量数据类型无关,不同类型指针变量所占的内存空间大小都是相同的。指针变量被定义后,指针变量也要先赋值再使用,当然指针变量被赋的值应该是地址。
在C语言中主要用两种方法使用内存:一种是由编译系统分配的内存区;另一种是用内存动态分配方式,留给程序动态分配的存储区。
动态分配的存储区在用户的程序之外,不是由编译系统分配的,而是由用户在程序中通过动态分配获取的。使用动态内存分配能有效地使用内存,同一段内存区域可以被多次使用,使用时申请,用完就释放。
补充:内存分配有关的函数
动态内存分配函数
头文件:#include <stdlib.h>
malloc()函数
该函数用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址,在内存的动态存储区中分配一个长度为size的连续空间。此函数的返回值是分配区域的起始地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置。
函数原型
void *malloc(unsigned int size);
返回值:如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。
当内存不再使用时,应使用free()函数将内存块释放。
calloc()函数
该函数功能为在内存的动态存储区中分配num个长度为size的连续空间。如果要求的空间无效,那么此函数返回指针。在分配了内存之后,calloc()函数会通过将所有位设置为0的方式进行初始化。比如,调用calloc()函数为n个整数的数组分配存储空间,且保证所有整数初始化为0。
函数原型:
void* calloc(unsigned int num,unsigned int size);
calloc与malloc的区别在于,在动态分配完内存后,自动初始化该内存空间为零,而malloc不做初始化,分配到的空间中的数据是随机数据。
realloc()函数
该函数将先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。
函数原型:
void *realloc(void *mem_address, unsigned int newsize);
返回值:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
free()函数
释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。如果传递的参数是一个空指针,则不会执行任何动作。该函数不返回任何值。
函数原型:
Copy
void free(void *ptr)
指针的分类
一级指针、二级指针、指向一维数组的指针、二维数组的行指针和列指针、指向函数的指针[如int (*p)(int,int)
]。
一些概念:
- 解引用
- 二维数组的行指针和列指针
- 数组指针&指针数组
- 二级指针
- 字符串:字符串常量&字符串数组&字符串指针
解引用
实际上就是我所理解的取值运算符,取出地址对应的那个值,“解析出那个值”。
感觉*(或[])和&是一对互逆运算符。
注:指针和数组关系密切,事实上,数组元素的获取可以用指针的形式,[]和*相当
什么意思呢?看如下代码
int a[3];
int *p = a;
访问a数组的第二个元素a[1]有如下方法:
a[1] *(a+1)
从这里可以看出 []和*符号可以互相转化,看作等同
此外,由指针的定义,p = a 所以a和p是等价的
因此a[1] *(a+1) p[1] *(p+1) 这四个表达式都是等价的.
用 地址相等 进行等价替代,用*/[]和&的互逆关系,可以比较清楚地分析二维数组行指针的相关表达,详见附录III。
二级指针:
即指针的指针,需要解引用两次才能获得值。
int a = 1;
int *p = &a;
int **pp = &p; //pp为指向p的二级指针
printf("%d",pp); //p的内存 也是a内存地址的内存地址
printf("%d",*pp); // 等价于p 也就是a的内存
printf("%d",**pp); // 值为a 两次解引用
注:二级指针其实在二维数组的行指针中有它的身影。而行指针本质上是数组指针,数组指针又可以构成指针数组,所以其实三者在形式上可能有些相像...
数组指针和指针数组
- 指针数组:顾名思义,数组是中心词,数组的元素是指针,见下例:
int *p[10]; //数据类型为int*[]
- 数组指针:顾名思义,就是指向数组的指针,见下例:
int(*p)[10]; //数据类型为int(*)[].
- 二者的应用(我没找到什么特别的应用)
- 一个数组指针常常作为二维数组的行指针
- 二维数组行指针构成的数组,可以说是指针数组
附录III有实例辨析。
二维数组的行指针和列指针
通过行指针访问二维数组:
要点:二维数组每一行都是一维数组,因此可用数组指针来表示二维数组的行指针。
int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
int(*p)[3] = a; //数组指针指向了数组的第一行(的数组)。
//出现了!数组指针!
如果要访问a[0][0] 可以用:
a[0][0] //采用原滋原味的数组形式!
(*a)[0] //前面说过,[]可以和*替换,因此有了这种不三不四的杂糅表示方法。
**a // 将两个[]都换掉以后变成了一个二级指针..
p[0][0],(*p)[0],**p //前面说过,地址相同可以等价替换,因此把a换成p也可以.
类似地,如果要获得 a[1][2],可以这么表示:
a[1][2];
(*a+1)[2];
*(*(a+1)+2);
以及p[1][2],(*p+1)[2],*((*p+1)+2)
注:上述代码中,p[3]这个数组可看作 指针数组?!
通过列指针访问二维数组
int a[3][3];
int *p = &a[0][0]; //或者int *p = a;
访问a[i][j]用:*(p+j*i+j);//和二维数组地址计算一样了。
推荐一篇很好的Blog
https://blog.csdn.net/zzy979481894/article/details/124678972
字符串相关
- 字符串常量:
char *p = "hello"; //指针p指向字符串常量"hello" 可以输出,截取,但是不能修改,因为是常量。
puts(p);
- 字符数组
char s[]="hello";
char *p = s; //字符指针指向一个字符串数组,元素依次为:h,e,l,l,o,0.
- 字符指针 即
char *p = ...;
Chapter 7 文件
没啥可说的,放两个优秀博客:
https://www.cnblogs.com/lily233/p/12044142.html
https://www.cnblogs.com/linfangnan/p/12046031.html
常见算法代码示例
辗转相除法
用于求a,b的最大公约数
input a,b
while(a%b != 0){
int r = a%b;
a = b;
b = r;
}
printf("%d",b);
冒泡排序:
原理:从左到右,相邻元素进行比较、交换。第一轮操作后最大的数一定在最右边,于是第二轮只需对剩下n-1个数进行重复得操作,第二轮结束后第二大的也排序正确,如此进行直到全部排序正确。由于每一轮可以确定一个元素的位置,所以一共需要n轮(最坏的情况,n趟)。
//input a[]
for (i = 1; i < n; i++){//n轮
for (j = 0; j < n - i; j++){ //每一轮需要排序的元素
if (a[j] > a[j + 1]){
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
选择法排序
input a[];
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
if(a[i]<a[j]){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}//从a[i]后面的项中依次选出最小的项排在下标为i的位置,故曰选择法排序。
}
}
插入法排序:
思想:先排前两项,然后后面的逐项找合适的位置插入,不断形成长度在增长的有序序列。
代码实现:利用函数。假设是从小到大的升序。
for(int i=0;i<n;i++)
scanf("%d",&a[i]); //Input array a
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(a[i]>a[j]&&a[i]<a[j+1])
insert(a,n,a[i],j+1);
}
}//对于每一项a[i],扫描前面的各项看a[i]所处的位置在哪,如果有的话就插入(下面实现insert函数),如果不合适就不进行操作。
void insert(a[],n,x,i){ //把x插入有n项的数组a[n]中的下标为i的位置。
for(int j=n-1;j>=i;j--){
a[j+1] = a[j]; //往后挪
}
a[i] = x;//插入
}
数组的输入与遍历
一维数组:一个for循环
二维数组:一个套一个
注意二维数组可以当作矩阵使用,输出的时候格式为:
for(int i=0;i<n;i++){
for(int j = 0;j<n;j++){
printf("%d",a[i][j]);
}
printf("\n");
}
把x插入到数组a下标为k的位置
input a[] //假设已经有n个元素
for(int i = n-1;i>=k;i--){
a[i+1] = a[i];
}
a[k] = x;
二分查找
(课件上源码)
#include <stdio.h>
#define MAXN 10
int main()
{
int found, i, left, mid, n, right, x;
int a[MAXN];
scanf("%d %d", &n, &x);
for (i = 0; i < n; i++){
scanf("%d", &a[i]);
}
found = left = 0;
right = n-1;
while (left <= right){
mid = (left + right) / 2;
if (x == a[mid]){
found = 1;
break;
}
else if (x < a[mid]){
right = mid - 1;
}
else{
left = mid + 1;
}
}
if (found != 0){
printf("%d\n", mid);
}
else{
printf("Not Found\n");
}
return 0;
}
数列求和
- 明确通项公式(注意数据类型,是int还是double,一般错在通项搞错了)
- 明确是指定步长求和(用for循环)还是精度求和(用while)
- 创建sum变量,并初始化为0
- 循环求和。
- 注意结果要的精度
- 数据类型基本用int/double
附录I 理论考试题目集
1.二维数组的越界计算问题
a[i][j]的地址 = X+i*sizeof(a[0])+j*sizeof(a[i][j])
2.!("01/24/2019"+5)[5]
相当于
const char *p = "01/24/2019";
!(p+5)[5] 即!p[10]
p[10]是'\0'字符,其值是0,!0得1
3.看清表达式本身会不会改变变量的值,最易忽略的是:++是会改变值的
此外还有++前或后置的区别。前置代表先加后取,后置则先取再加。
关于指针的自增
*p++//*++p//++*p//*(p++)
p++是先取p的值打印,然后指针p中存储的地址自增。
(p)++是先取p的值打印,然后让这个值自增。
(p++)和p++是一样的运行顺序。
++p和++(p)是一样的,都是先取*p的值,让这个值自增,再打印。
4.Given:short s[][5]={301,302,303,304,305,306,307,308,309,0};
,
the values of sizeof(s) and strlen((char *)s) will be:20,18(?)respectively.
5.printf %d输出有符号十进制 %u输出无符号 所以%d+强转unsigned是没用的
6.注意全局变量的生命周期
7.指针相减得的结果是两者之间元素的个数+1.
8.The statement printf("%%d%d", 012); will print out :【%d10】
012为八进制(0开头的要小心),%d输出十进制
附录II 上机编程失误
类型不匹配
- 输入输出函数的数据类型%d,%lf等-->全变成了0 - 自定义函数的类型与返回值不匹配-->数字溢出 - 注意流程控制!有些功能在查找的时候只要求实现一次的要用break跳出循环。 - 注意二维数组的灵活使用,本质上二维数组就是一个数表,按规律对其每一项进行赋值就可以输出一个规律的数表。 //一定要检查函数的类型和返回值的类型是否匹配。。。。附录III 指针、数组再说明
因为太长了所以把文章全部放后面了。
数据类型int*,int**,int*[],(int*)[],(int*)()辨析
- int * int**
这种就是最简单的指针指向一个对象。
int *直接指向一个对象;int **指向一个指针
解引用的时候前者解一次后者解两次罢了。
int initialInt = 10;
int *initialIntPoint = &initialInt; 一个指针对象指向对象
int **initialIntPointPoint = &initialIntPoint; 这个是双指针,指向一个指针对象
cout << "对象: " << initialInt << endl;
cout << "对象地址: " << &initialInt << endl;
cout << "指针对象: " << initialIntPoint << endl; 对象的地址
cout << "指针解引用: " << *initialIntPoint << endl; 解引用获得对象的值
cout << "双指针对象: " << *initialIntPointPoint << endl; 指针对象的地址
cout << "双指针解引用: " << *initialIntPointPoint << endl;
cout << "双指针双解引用: " << **initialIntPointPoin·t << endl << endl; 使用双解引用获得原本的值
- 一维数组和二维数组的解引用
int initialArray[10] = { 1, 5, 10, 15, 20 };
cout << "数组首地址: " << initialArray << endl; 就一维,所以直接就是第一个的位置
cout << "数组首地址解引用: " << *initialArray << endl;
cout << "数组首地址+1解引用: " << *(initialArray + 1) << endl << endl;
//指向一维数组的指针就像一级指针一样,通过一些运算可以遍历数组。
int initialTwoArray[2][4] = { { 2, 5, 6, 8 }, { 22, 55, 66, 88 } };
cout << "二维数组第一行地址: " << initialTwoArray << endl; 相当于指向某一维
cout << "二维数组第一行第一个地址: " << *initialTwoArray << endl; 指向某一维的第一个的地址
cout << "二维数组第一行第一个地址解引用(第一个的值): " << **initialTwoArray << endl; 某一维的第一个的值
cout << "二维数组第一行地址第一个地址+1: " << *initialTwoArray + 1 << endl; 指向某一维的第二个地址
cout << "二维数组第一行地址第一个地址+1解引用(第二个值): " << *(*initialTwoArray + 1) << endl;
cout << "二维数组第一行地址+1(第二行): "<<initialTwoArray + 1 << endl; 指向第二维
cout << "二维数组第一行地址+1的第一个地址(第二行第一个的地址): " << *(initialTwoArray + 1) << endl; 第二维的第一个的地址
cout << "二维数组第一行地址+1的第一个地址解引用(第二行第一个的值): " << **(initialTwoArray + 1) << endl; 第二维的第一个的值
cout << "示例:第二行的第二个的值: "<<*(*(initialTwoArray + 1) + 1) << endl << endl; 第一维加一的解引用表示第二维的首地址 再加一的解引用表示第二维的第二个的值
//指向二维数组的指针就像二级指针一样,原来的指针(行指针)指向某一行的地址(即指向一维数组),解引用一次以后指向某一行第一个的地址,再解一次可以得到某一行第某个的元素值。
- int *p[] 指针数组
int *initialPointArray[10];
*initialPointArray = initialArray; //由于指针本来就相当于数组,所以把initialArray的首地址赋给了ipa的首元素(指针)
cout << "指向一个指针数组的地址: " << initialPointArray << endl;
cout << "指向一个指针数组的地址的第一个指针的地址(数组第一个对象位置): " << *initialPointArray << endl;
cout << "指向一个指针数组的地址的第一个指针的地址的解引用(数组第一个对象值): " << **initialPointArray << endl;
cout << "指向一个指针数组的地址的第二个指针的地址的解引用(数组第二个对象值): " << *(*initialPointArray + 1) << endl << endl;
//理解:把*initialPointArray就看作initialArray(进行"等量代换")
//ipa是指针的数组,相当于二级指针、二维数组。这里ipa[0][i]=a[i]。ipa[1]没有意义
- int (*p)[10] 数组指针
int(*initialArrayPoint)[10];
initialArrayPoint = &initialArray;
cout << "指针指向一个位数为10的数组:" << endl; 表示指向一个一维数组
cout << "指针指向一个数组的指针地址: " << initialArrayPoint << endl; 代表这个数组第一个位置
cout << "指针指向一个数组的指针地址的第一个地址: " << *initialArrayPoint << endl; 这个数组第一个位置的地址
cout << "指针指向一个数组的指针地址的第一个地址的值: " << **initialArrayPoint << endl << endl; 这个数组第一个位置的值
int(*initialArrayPoint2)[4];
initialArrayPoint2 = *(&initialTwoArray);
cout << "指针指向一个位数为4的二维数组:" << endl; 表示指向一个二维数组
cout << "指针指向一维数组的指针地址: " << initialArrayPoint2 << endl; 二维数组的第一维
cout << "指针指向一维数组的指针地址的第一个地址: " << *initialArrayPoint2 << endl; 二维数组第一维的第一个地址
cout << "指针指向一维数组的指针地址的第一个地址的值: " << **initialArrayPoint2 << endl; 二维数组的第一维的第一个的值
cout << "指针指向二维数组的指针地址: " << initialArrayPoint2+1 << endl; 二维数组的第二维
cout << "指针指向二维数组的指针地址的第一个地址: " << *(initialArrayPoint2+1) << endl;
cout << "指针指向二维数组的指针地址的第一个地址的值: " << **(initialArrayPoint2+1) << endl; 二维数组第二维的第一个的值
//好像都可以用等量替换来看,*和&就像求导和积分符号一样互逆...
注:数组名称只有在:①被sizeof()
运算符操作②被取地址&
运算符操作时才不看做首元素的地址,而代表整个数组。
指针和数组的区别
https://blog.csdn.net/asdfsadfasdfsa/article/details/87896436
写在word文档里了,因为有图片
几个字符(串)处理函数
https://blog.csdn.net/qq_35904005/article/details/125781050
字符:isalpha isdigit isspace isupper islower...
字符串:
- strlen():计算“有效长度”。返回值是“无符号整型”。操作对象必须包含0。
实现:计数器方法(cnt). - strcpy(d,s)将s的内容拷贝到d中.同样要保证有0,0也会拷贝,d不能是字符串常量。
- strcat(d,s)将s的内容追加到d末尾。catenate:连接。
实现:1.找'\0',2.拷贝。 - strcmp(str1,str2) compare.将str1和str2的字符逐个比较,大于则返回大于0的数字,等于返回0,小于返回小于0的数。
- strncpy.strncmp.strncat在原有函数上多加一个参数n限定长度。
附录IV 进位制
十进制、二进制、八进制、十六进制
常用数制之间的互相转换方法及算法思想(附加题)
主要是十进制和二进制互化。
十进制化二进制,短除法
二进制化十进制,数组+求和
十进制小数化规格化二进制小数:先/2或者*2到[1,2]之间获取指数信息,再按规则转化
二进制小数化十进制:数组+数列求和
二进制化八进制,"取三合一"
二进制化十六进制,"取四合一"