《算法笔记》总结一

     

目录

第二章

1. 定义变量时尽可能赋初值,避免在程序时出现未知访问错误!!!(不能想当然的赖编译器的默认初始化)

1.1C程序的内存布局。

1.2内存管理的目的及建议

1.3 note that

2. ASCII码的编程应用

3.输入输出的运用技巧及坑点

3.1 scanf()

3.2 getchar( )/getc(stdin)、putchar( )/putc(ch,stdout)

3.3 gets、gets_s、puts、fgets

3.4 sscanf与sprintf

第三章 入门篇(1)——入门模拟

####注意循环中的变量的作用域,

###整型数据分割技巧:

####进制转换方法对于一个P进制的数,如果要转换为Q进制,需要分为两步:


                                                                           

第二章

1. 定义变量时尽可能赋初值,避免在程序时出现未知访问错误!!!(不能想当然的赖编译器的默认初始化)

扩展知识点 内存管理 地址映射~(早期看《c程序设计语言》也讲到相关知识点,但是编写时没注意到痛点,谨记今后不再犯)

定义变量时,C和C++有着显著的区别。这两种语言都要求变量使用前必须定义,但是C(和很多其他的传统过程语言)强制在作用域的开始处就定义所有的变量(c99之前),以便在编译器创建一个块时,能给所有这些变量分配空间。

下图是C语言的内存布局结构,Memory layout of c program中有着详细的介绍。(https://www.geeksforgeeks.org/memory-layout-of-c-program/

1.1C程序的内存布局。

1.文本段 (Text segment)
2.初始化数据段(Initialized data segment
3.未初始化数据段(
 Uninitialized data segment
4.堆栈(
 Stack
5.堆(
Heap

 

1.文本段:

  文本段,也称为代码段或简称为文本段,是目标文件或内存中包含可执行指令的程序的一部分。作为存储区域,文本段可以放置在堆或堆栈下方,以防止堆和堆栈溢出覆盖它。通常,文本段是可共享的,因此对于频繁执行的程序(例如文本编辑器,C编译器,shell等),只需要一个副本就可以存储在内存中。此外,文本段通常是只读的,以防止程序意外修改其指令。假如同时有多个编译任务在执行,这些编译任务会共享编译器的代码区,但同时各个编译任务又有自己独立的区域。

2.初始化数据段:
初始化数据段,通常简称为数据段。数据段是程序的虚拟地址空间的一部分,其包含由程序员初始化的全局变量和静态变量。

请注意,数据段不是只读的,因为变量的值可以在运行时更改。

该段可以进一步分为初始化只读区域和初始化读写区域。

例如,在C中由char s [] =“hello world”定义的全局字符串和在main(即全局)之外的int debug = 1之类的C语句将存储在初始化的读写区域中。并且像const char * string =“hello world”这样的全局C语句使得字符串文字“hello world”存储在初始化的只读区域中,字符指针变量字符串存储在初始化的读写区域中。

例如:static int i = 10将存储在数据段中,global int i = 10也将存储在数据段中

3.未初始化的数据段:
未初始化的数据段,通常称为“bss”段,以一个代表“由符号开始的块”的古代汇编运算符命名。该段中的数据在程序启动之前由内核初始化为算术0执行

未初始化的数据从数据段的末尾开始,包含初始化为零或在源代码中没有显式初始化的所有全局变量和静态变量。

例如,变量声明为static int i; 将包含在BSS部分中。
例如,一个声明为int j的全局变量; 将包含在BSS部分中。

4.堆栈:
堆栈区域传统上与堆区域相邻并向相反方向增长; 当堆栈指针遇到堆指针时,空闲内存耗尽。(使用现代大地址空间和虚拟内存技术,它们几乎可以放置在任何地方,但它们通常仍会朝着相反的方向发展。)

堆栈区域包含程序堆栈,LIFO结构,通常位于存储器的较高部分。在标准的PC x86计算机体系结构上,它向零地址发展; 在其他一些架构上,它朝着相反的方向发展。“堆栈指针”寄存器跟踪堆栈的顶部; 每次将值“推”到堆栈上时都会调整它。为一个函数调用推送的值集称为“堆栈帧”; 堆栈帧至少包含返回地址。

存储自动变量的堆栈,以及每次调用函数时保存的信息。每次调用函数时,返回的地址和有关调用者环境的某些信息(例如某些机器寄存器)都会保存在堆栈中。然后,新调用的函数在堆栈上为其自动和临时变量分配空间。这就是C中递归函数的工作方式。每次递归函数调用自身时,都会使用新的堆栈帧,因此一组变量不会干扰来自该函数的另一个实例的变量。

堆区域由malloc,calloc,realloc和free管理,它们可以使用brksbrk系统调用来调整其大小(请注意,不需要使用brk / sbrk和单个“堆区域”来实现malloc / calloc / realloc / free的契约;它们也可以使用mmap / munmap来实现,以保留/ 取消将虚拟内存的潜在非连续区域保留到进程的“ 虚拟地址空间”中。堆区域由进程中的所有线程,共享库和动态加载的模块共享。(https://en.wikipedia.org/wiki/Data_segment

5.堆:
堆是通常发生动态内存分配的段。

堆区域从BSS段的末尾开始,并从那里增长到更大的地址。堆区域由malloc,realloc和free管理,可以使用brk和sbrk系统调用来调整其大小(注意使用brk / sbrk和单个“堆区域”不需要履行malloc / realloc / free的合同;它们也可以使用mmap实现,以将可能不连续的虚拟内存区域保留到进程的“虚拟地址空间”中。堆区域由进程中的所有共享库和动态加载的模块共享。

1.2内存管理的目的及建议

https://www.cnblogs.com/yif1991/p/5049638.html

学习内存管理就是为了知道日后怎么样在合适的时候管理我们的内存。那么问题来了?什么时候用堆什么时候用栈呢?一般遵循以下三个原则:

  • 如果明确知道数据占用多少内存,那么数据量较小时用栈,较大时用堆;
  • 如果不知道数据量大小(可能需要占用较大内存),最好用堆(因为这样保险些);
  • 如果需要动态创建数组,则用堆。

扩展

操作系统在管理内存时,最小单位不是字节,而是内存页(32位操作系统的内存页一般是4K)。比如,初次申请1K内存,操作系统会分配1个内存页,也就是4K内存。4K是一个折中的选择,因为:内存页越大,内存浪费越多,但操作系统内存调度效率高,不用频繁分配和释放内存;内存页越小,内存浪费越少,但操作系统内存调度效率低,需要频繁分配和释放内存。嵌入式系统的内存内存资源很稀缺,其内存页会更小,因此在嵌入式开发当中需要特别注意。

C语言 内存管理详解http://club.topsage.com/thread-443540-1-1.html

1.3 note that

https://bbs.csdn.net/topics/392175315

其实电脑开机后物理内存的每个字节中都有值且都是可读写的,从来不会因为所谓的new、delete或malloc、free而被创建、销毁。区别仅在于操作系统内存管理模块在你读写时是否能发现并是否采取相应动作而已。操作系统管理内存的粒度不是字节而是页,一页通常为4KB。
VMMap 是进程虚拟和物理内存分析实用工具。http://technet.microsoft.com/zh-cn/sysinternals/dd535533

不能指望是否报错来判断是否越界;要指望是否触发数据断点:

2. ASCII码的编程应用

标准的ASCII码的范围是0~127 

常用小技巧:0~9、A~Z、a~z的ASCII码分别是48~57、65~90、97~122 小写字母比大写字母的ASCII码大32

3.输入输出的运用技巧及坑点

3.1 scanf()

####函数原型:int scanf(const char * restrict format,...);

!!!!!!函数 scanf() 是从标准输入流stdio (标准输入设备,一般指向键盘)中读内容的通用子程序,可以说明的格式读入多个字符,并保存在对应地址的变量中。 

(键盘的输入缓冲区由键盘驱动或键盘控制器实现,是内存的一块区域。按下回车后,数据从键盘的输入缓冲区,进入流缓冲区(系统给它另外单独开了一块内存不过跟键盘那个不在同一个位置),进而形成输入流,提取运算符">>"才能从中提取数据。
输入流的本质是文件,打开流即打开文件,如标准输入流是stdin对应的文件描述符为0,这是在系统层面,语言层面的输入流会对应到系统层面的输入流。)!!!!!!!!

####扩展阅读(对C语言输入输出流和缓冲区的深入理解):https://wenku.baidu.com/view/99e57b3c360cba1aa911da78.html

函数的第一个参数是格式字符串,它指定了输入的格式,并按照格式说明符解析输入对应位置的信息并存储于可变参数列表中对应的指针所指位置。每一个指针要求非空,并且与字符串中的格式符一一顺次对应。

返回值 :scanf函数返回成功读入的数据项数,读入数据时遇到了“文件结束”则返回EOF。

空白字符和非空白字符

空白符:空白字符会使scanf函数在读操作中略去输入中的一个或多个空白字符。

非空白字符:一个非空白字符会使scanf()函数在读入时剔除掉与这个非空白字符相同的字符。

####注意问题:

    1.可以在格式化字符串中的"%"各格式化规定符之间加入一个整数,表示任何读操作中的最大位数  

    2.scanf中要求给出变量地址,如给出变量名则会出错!!!!!!!!!!!!(这一点千万不能忘了,不然会郁闷死)

                  如 scanf("%d",a);是非法的,应改为scanf("%d",&a);才是合法的。

    3在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔,则可用空格,TAB或回车作间隔。

               C编译在碰到空格,TAB,回车或非法数据(如对“%d”输入“12A”时,A即为非法数据)时即认为该数据结束。.

    4.如何让scanf()函数正确接受有空格的字符串?

      scanf("%[^\n]",str);

    5.键盘缓冲区残余信息问题

                  fflush(stdin) ;getch();getchar(); 

    6.处理scanf()函数误输入造成程序死锁或出错

                  scanf()函数执行成功时的返回值是成功读取的变量数,也就是说,你这个scanf()函数有几个变量,如果scanf()函数全部正常读取,它就返回几。但这里还要注意另一个问题,如果输入了非法数据,键盘缓冲区就可能还个有残余信息问题。

3.2 getchar( )/getc(stdin)、putchar( )/putc(ch,stdout)

getchar由宏实现:#define getchar() getc(stdin)。getchar有一个int型的返回值。当程序调用getchar时.程序就等着用户按键。用户输入的字符被存放在键盘缓冲区中。直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的字符的ASCII码,若文件结尾(End-Of-File)则返回-1(EOF),且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完后,才等待用户按键。

#include<stdio.h>
int main{
    char c1,c2,c3;  
    c1=getchar();
    getchar();    //第二个字符‘b’虽然被接受了,但是没有将它存储在某个变量中;
    c2=getchar();
    c3=getchar();
    putchar(c1);//putchar()输出单个字符
    putchar(c1);
    putchar(c2);
    putchar(c3);
    return 0;  
}

输入数据:

abcd

输出结果:

acd

如果输入“ab”,然后在按<Enter>键,再输入‘c’,再按<Enter>键,输出结果会是

a

c

3.3 gets、gets_s、puts、fgets


char *gets( char *str );

  (C11 中移除)

char *gets_s( char *str, rsize_t n );

  (C11 起) 
(可选)

1) 从 stdin 读入 str 所指向的字符数组,直到发现换行符或出现文件尾。在读入数组的最后一个字符后立即写入空字符。换行符被舍弃,但不会存储于缓冲区中。

2) 从 stdin 读取字符直到发现换行符或出现文件尾。至多写入 n-1 个字符到 str 所指向的数组,并始终写入空终止字符(除非 str 是空指针)。若发现换行符,则忽略它并且不将它计入写入缓冲区的字符数。

在运行时检测下列错误,并调用当前安装的制约处理函数:

  1. n 为零
  2. n 大于 RSIZE_MAX
  3. str 是空指针
  4. 在存储 n-1 个字符到缓冲区后没有遇到换行符或文件尾。

任何情况下,gets_s 首先结束读取并忽略来自 stdin 的字符,直到换行符、文件尾条件,或在调用制约处理前的读取错误。

同所有边界检查函数, gets_s 仅若实现定义了 __STDC_LIB_EXT1__ ,且用户在包含 <stdio.h> 前定义 __STDC_WANT_LIB_EXT1__ 为整数常量 1 才保证可用。

注意:gets识别换行符\n作为输入结束,因此scanf完一个整数后,如果要使用gets,需要先用getchar接收整数后的换行符),

####scanf结束后的Enter保留在了输入缓冲区中!!!!


puts 

int puts( const char *str );

   

写入每个来自空终止字符串 str 的字符及附加换行符 '\n' 到输出流 stdout ,如同以重复执行 putc 写入。

不写入来自 str 的空终止字符。

####注意:puts 函数后附一个换行字符到输出,而 fputs 不这么做。

不同的实现返回不同的非负数:一些返回最后写入的字符,一些返回写入的字符数(或若字符串长于 INT_MAX 则返回它),一些简单地返回非负常量。

在重定向 stdout 到文件时,导致 puts 失败的典型原因是用尽了文件系统的空间

gets()被抛弃,那我们用什么来代替它的功能呢?

C11标准新增了gets_s()函数可以代替gets()函数,但是,该函数是stdio.h输入输出函数系类中的可选扩展,因此,即使编译器支持C11标准,也有可能不支持gets_s()函数。

其实我们可以用c语言中的fgets()函数来代替gets()

我们先看一下函数原型声明:

char *fgets(char *buf, int bufsize, FILE *stream);

注意一下第二个参数bufsize,这个参数就限制了读取的字符的个数,这就可以解决gets()函数的缺陷。

我们知道fgets() 函数主要用于读取文件,如果要读取键盘,则stream参数应该为stdin,

需要注意的是,如果bufsize设置为n,那么fgets()函数最多读取n-1个字符,之所以用“最多”这个词是因为,如果在之前遇到了换行符,fgets函数也会返回。

还有一点就是,fgets()函数会读取换行符(这一点和gets函数不同),当读取结束后,fgets函数会为buf在末尾添加一个空字符作为字符串的结束。

3.4 sscanf与sprintf

3.4.1 sscanf使用

sscanf(str,“%d”,&n);把字符数组str中的内容以“%d”的格式写到n中(和scanf一样从左到右)

fprintf(str,"%d",n);把n以“d%"的格式写的字符数组中(还是从右至左)(类似sscanf)

第三章 入门篇(1)——入门模拟

1.P89_PAT_B1036:

####注意循环中的变量的作用域,

如无关联的变量,在对应块中使用不同变量名,可以避免这类问题的发生

2.P91_codeup_1928:

直接求解两个日期的相差天数,实现较复杂,采取令日期相加,直到相等的方法入手

note1:bool isLeap(int year){//判断是否是闰年

return(year%4==0&&year%100!=0)||(year%400==0);

###整型数据分割技巧:


while (num>0):
    l.append(n%10)//拆分为n位对应的除数为10^n
    n=n/10
l=list(reversed(l))#将顺序倒过

####进制转换方法对于一个P进制的数,如果要转换为Q进制,需要分为两步:

1.将p进制数x转换为十进制整数y.

一个十进制整数x转换为十进制数y。

对于一个十进制的数y=d1d2...dn,它可以写成这个形式:

y=d1*10^(n-1)+d2*10^(n-2)+...+dn-1*10+dn

同样的,如果P进制数x为a1a2...an,那么它写成下面这个形式之后使用十进制的加法和乘法,就可以转换为十进制数y:

y=a1*P^(n-1)+a2*P^(n-2)+...+an-1*P+an

而这个公式可以用下面的循环实现:

int y=0,product=1;     //product在循环中会不断乘P,得到1、P、P^2、P^3...
while(x!=0){
    y=y+(x%10)*product;//x%10是为了每次获取x的个位数
    x=x/10;           //去掉x的个位
    product=product*10;
}

2.将十进制数y转换为Q进制数z。

采用“除基取余法”。所谓的“基”,是指将要转换成的进制Q,因此除基取余的意思就是每次将待转换数除以Q,然后将得到的余数作为低位存储,而商则继续除以Q并进行上面的操作,最后当商为0时,将所有位从高到低输出就可以得到z。举一个例子,现在将十进制数11转换为二进制数:

11除以2,得商为5,余数为1;

5除以2,得商为2,余数为1;

2除以2,得商为1,余数为0;

1除以2,得商为0,余数为1,算法终止。

将余数从后往前输出,得1011即为11的二进制数。

由此可以得到实现的代码(将十进制数y转换为Q进制,结果放于数组z):

int z[40],num=0;   //数组z存放Q进制数y的每一位,num为位数
do{
    z[num++]=y%Q; //除基取余
    y=y/Q;
}while(y!=0)

以上内容来自相关blog、以及相关网站网站等整理形成  文中已给出链接,或许有些遗漏。但通过相关官方文档即可得到相关知识点内容,只是阅读上的问题了,碰见语言相关问题 ,推进看官方文档学习http://www.cplusplus.com/reference/cmath/atan/

https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5

猜你喜欢

转载自blog.csdn.net/idealhunting/article/details/82711178