秋程设

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种情况:

  1. 11位指数不为00000000000和11111111111,即在00000000001 ~ 11111111110(1 ~ 2046)范围,这被称为规格化。
  2. 指数值为00000000000(0),这被称为非规格化
  3. 指数值为11111111111(2047),这是特殊值,有两种情况:
  • 当52位小数部分f全为0时,若符号位是0,则表示+Infinity(正无穷),若符号位是1,则表示-Infinity(负无穷)
  • 当52位小数部分f不全为0时,表示NaN(Not a Number)
    规格化
    规格化下,浮点数的形式可表示为:

\[(-1)^s·1.bbbbb...(cccc...)·2^{e-1023},(0<e<2047) \]

s为0或1,0表示正数,1表示负数,对应1bit的符号位
f为52位有效位,其中的每一位b是0或1,对应52bit的小数部分(不足52位补0)
c是超出52位的部分(如果有的话,需要舍入(精度会丢失),规则下述)
e为十进制数值,其11位的二进制数值对应11bit的指数部分
1023为移码,移码值为

\[ 2^{n-1}-1 \]

这里的\(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

运算符以及优先级

  1. 算数运算符 +、-、*、/、%
    类型大致一致才能进行运算,整数的除法会自动取整。
    可以强制类型转换,但是有可能损失精度!
  2. 关系运算符 >,>=,<,<=/==,!=
  3. 逻辑运算符 && || (!为单目)
    需要注意的是逻辑运算符在能确定结果时就不会往下计算,是个懒鬼。
  4. 三目运算符 X?A:B 与if-else等价
  5. 单目运算符
  • +,-
  • 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.
  1. 逗号运算符 优先级最低 (a,b,c)-->值为c
    • 我也不知道这东西发明出来有什么用
  2. (补充)按位运算符:
    • & 按位与
      • & 是个双目运算符,双目是指参与运算的对应二进制位,它的逻辑也比较容易理解:对应二进制位均为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
  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,生存周期是整个程序运行周期.

编译预处理

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*)()辨析

https://blog.csdn.net/qq_46423166/article/details/115358311

  • 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]之间获取指数信息,再按规则转化
二进制小数化十进制:数组+数列求和
二进制化八进制,"取三合一"
二进制化十六进制,"取四合一"

posted @ 2023-04-18 15:57  yuanhongyi2004  阅读(309)  评论(0编辑  收藏  举报