更新的有点慢了,主要是学习方面有一些问题,想先学扎实点,好在最后成功拿下,来看看第二周的总结吧
C预言的三种基本控制结构
顺序结构
选择结构
if语句
(1)
if(表达式语句)
{
}
只要表达式语句为真,就执行语句
比如:
int a=200;
int b=100;
if(a > b)
{
a = a + b;
printf("%d\n",a);
}
(2)
if (表达式语句)
{
语句1;
}else{
语句2;
}
当表达式的值为真时执行语句1,否则执行语句2
(3)
If(表达式语句1)
{
语句1;
}else if(表达式2){
语句2;
……
}else if(表达式n)
{
语句n;
}else{
语句m;
}
比如:
if(num<50)
{
cost = 100;
}else if(num<100)// [50,100)
{
cost = 50;
}else if(num<200)
{
cost = 30;
}else if(num<300)
{
cost = 20;
}else
{
cost = 0;
}
(4)
if的嵌套:条件里面再分条件
形式:
if()
if() 语句
else 语句
else
if() 语句
else 语句
else的匹配问题:
else总是与它上面的,最近的,同级未匹配的if语句配对
switch语句(开关语句)
形式:
switch(表达式)
{
case 常量表达式1:
语句1
case 常量表达式2:
语句2
break;
...
case 常量表达式n
语句n
default:
语句m
}
(1)"表达式"的值必须为整数值(整型,字符型,枚举),即可以列举的值
(2)“常量表达式"的值必须为整数值,且每个case后面的常量表达式的值必须不相同。
(3)当表达式的值与某一个case后面的常量表达式的值相同时,就会打开"开关”,执行冒号后面的语句,直到有break语句就提前跳出switch,否则就轮到下一个case,且不会比较常量表达式的值,就直接执行冒号后面的语句(因为开关已经打开了)。
当所有的语句都执行完了,就结束了switch。
(4)break;提前跳出switch语句,常用的用法是每个case语句后都有一个break语句。
(5)当所有case后面的常量表达式都不匹配时,就会执行default后面的语句,且打开"开关
循环结构
goto语句
无条件跳转语句
goto 语句标号;
“语句标号”:再处于严重,把一个名字(C语言标识符)与某一行的地址相关联。
格式:在一行的开始处定义一个名字,然后再加一个冒号
look:
n++;
goto loop;
作用:
(1)与if语句向上跳转构成循环结构
(2)向后跳转可以跳出多重循环体等语句
注意:goto本身没有任何问题,但是goto语句是无条件跳转语句,应用太灵活,会使程序的可读性变差,应限制使用。
while语句
while(表达式)
语句
当表达式的值为真(非0),就执行语句,否则跳过循环体,
当进入循环体执行完语句后,再重复判断表达式的值...
"表达式":任意合法的表达式都可以
"语句":单语句,只有一个分号
复合语句,{}
编程建议:不管while后面有没有语句,请先写一个{},再到{}里面填内容
do while语句
do
语句
while(表达式);
先无条件执行一次循环体语句,然后再判断表达式的值,如果为真(非0),就继续执行
循环体语句...直到表达式的值为0,那么循环就不做了。
编程建议:不管do后面有没有语句,请先写一个{},再到{}里面填内容
for语句
for(表达式1;表达式2;表达式3)
语句
★执行过程:首次进入for循环体先执行一次表达式1,再判断表达式2的值,若值为真(非0),
执行语句,最后执行表达式3,一个循环结束。第二次循环起就不再执行表达式1,直接判断
表达式2的值,若为真就继续循环,若为假就跳出for循环。
for循环体完全等效于如下的while循环体
表达式1;
while(表达式2)
{
语句
表达式3;
}
唯一的区别是for循环体中的表达式123都可以为空,表达式2为空表示判断条件永远为真,
而while的表达式2不能为空。
★while循环与for循环的区别:一般来说,while更注重循环条件,for更注重循环次数。
当然它们是可以相互替换的。
for循环的嵌套:
int i,j,a=0;
for(i=0;i<5;i++)//外循环
{
for(j=0;j<4;j++)//内循环
{
printf("%d %d %d\n",i,j,a++);
}
}
break和continue语句
break语句:
(1)用于switch语句中,用于提前跳出switch语句
(2)用于循环体中(while/do~while/for),用于跳出它所属的循环
continue语句:
continue语句只能用于循环体中(while/do~while/for),用于提前结束本次循环,进入下次循环。
函数:function,功能模块
C语言中,函数是完成某个特定功能的指令序列的封装。
函数可以实现代码复用,以及模块化设计。
结构化程序设计主张把一个大任务,分成多个小任务的函数来完成。
“函数就是实现某个功能的指令序列”
static、 auto、 extern、 register、 return
函数设计
需求分析: 你得要知道完成什么事情
然后完成这个事情需要什么资源。
具体的功能实现–>算法
反馈一个结果
函数的实现(定义)
返回类型 函数名(输入参数列表)
{
语句
}
“返回类型”:函数返回值(return语句后面那个表达式的类型),一般是"单值"类型,
即基本类型或指针类型,函数也可以没有返回值,即void。如果不指定返回类型,默认为int型。
“函数名”:C语言标识符。把该名字与此函数相关联起来。
“输入参数列表”:功能模块的输入。格式如下:
参数类型1 参数名1,参数类型2 参数名2,…
int sum(int a, int b)
{
int c = a + b;
return c;
}
函数的调用
主调函数:调用其它函数的函数。
被调函数:别被人调用的函数
“实际参数”:在函数调用过程中,主调函数传递给被调函数的输入参数值,
我们称为实际参数。
“形式参数”:被调函数在定义时的参数。
函数的调用过程:
(1)把实参的值赋值给相应的形参。
(2)运行函数主体中的语句,直到遇到return语句返回或者执行完所有语句返回。
(3)函数调用返回后,该函数表达式的值为return后面表达式的值(也可以没有返回值)。
数据传递:
主–>被调函数: 参数
被–>主调函数: 返回值
主<->被调函数: 全局变量
Linux虚拟存储系统
一个系统中的进程是与其他进程共享CPU和主存资源的,为了更加有效地管理内存并且少出错,现代系统提供了一种对主存的抽象概念,叫虚拟内存(VM)。
进程是操作系统对一个正在运行的程序的一种抽象表示。
Linux为每个进程维护一个单独的虚拟地址空间。
1G = 1024MB = 10241024KB = 102410241024B = 1024102410248bit
虚拟地址空间的大小由系统的位数决定,若系统为32bit,则虚拟空间的大小为0x0~0XFFFFFFFFByte = 3G。虚拟机中的物理内存表示同一时间能够访问的最大空间。
虚拟地址空间最上面的区域保存操作系统中的代码和数据。地址空间的最底部区域是禁止访问区域(32bit系统00x08048000;64bit系统00x00400000)。上图地址从下往上增大。
程序代码和数据:在进程运行时就被规定了大小。.init(系统初始化代码)、.text(用户代码)和.bss(未初始化的静态数据)、.data(已初始化的静态数据)、.rodata(常量)这几个部分按照可执行目标文件初始化。
堆:在运行时动态地扩展和收缩。
共享库:用于存放标准C库和数学库等这样共享库的代码和数据的区域。
栈:用户栈在程序执行期间可以动态地扩展和收缩。当每次程序调用函数时,栈就会增长;当函数返回时,栈就会收缩。
内核虚拟存储器:操作系统的内核保留区,协调硬件和操作系统软件之间精密复杂的交互。
内存管理和递归函数
内存管理
(1)auto关键字
auto自动变量:普通局部栈变量,是自动存储,这种对象会自动创建和销毁 ,建议这个变量要放在堆栈上面,调用函数时分配内存,函数结束时释放内存。一般隐藏auto默认为自动存储类别。我们程序都变量大多是自动变量。
局部变量: 也叫自动变量, 它声明在函数开始, 生存于栈, 它的生命随着函数返回而结束。
auto用来修饰变量。
(2)static关键字
static用来修饰变量、函数。
static变量的使用
static函数只能在本文件中使用,其他文件无法使用
(3)extern关键字
外部变量声明,是指这是一个已在别的地方定义过的对象,这里只是对变量的一次重复引用,不会产生新的变量。系统的寄存器是有限制的,声明变量时如:register int i.这种存储类型可以用于频繁使用的变量。
extern用来修饰变量、函数。
(4)register关键字
寄存器变量,请求编译器将这个变量保存在CPU的寄存器中,从而加快程序的运行。register用来修饰变量。
第一句话:register是不能取址的。(&)
第二句话:声明变量为register, 编译器并不一定会将它处理为寄存器变量。
第三句话:现在一般的编译器(arm-linux-gcc)都忽略auto和register申明,现在的编译器自己能够区分最好将那些变量放置在寄存器中,那些放置在堆栈中;甚至于将一些变量有时存放在堆栈,有时存放在寄存器中。
(5)const关键字
const 是定义常变量的关键字。
const 定义的是变量,但又相当于常量;说它定义的是常量,但又有变量的属性,所以叫常变量。
①.符号常量和const关键字
const int a = 10;相当于int const a = 10;
用 const 定义的变量的值是不允许改变的,即不允许给它重新赋值,即使是赋相同的值也不可以。所以说它定义的是只读变量。这也就意味着必须在定义的时候就给它赋初值。
1. #define PI1 3.14
2. const double PI2 = 3.14;
3.
4. /*两种方式都可以用来定义全局变量。不过,第二种要比
5. 第一种方式要好,使用宏定义的变量,其信息一般以表格
6. 的形式储存在系统中,当我们在调试程序时,就有可能使
7. 得这个宏定义的变量反复出现在符号表中。而const修饰
8. 的变量会一直出现在符号表,使得我们调试方便许多。*/
#include <stdio.h>
#define PI1 3.14
const double PI2 = 3.14;
int main(int argc, char *argv[])
{
printf("%.2f, %.2f\n", PI1, PI2);
return 0;
}
②.修饰全局变量
#include <stdio.h>
const char n=8;//相当于char const n=8; .data
int main(void)
{
//测试被锁死的n变量对应地址标识的值是否能否被修改
n = 7;
/*
测试结果:
example.c: In function ‘main’:
example.c:13:4: error: assignment of read-only variable ‘n’
n = 7;
结论:当全局变量使用const关键字之后,n变量对应地址标识的会被锁死,无法修改。
*/
printf("n = %hhd\n", n);
}
③.修饰局部变量
#include <stdio.h>
int main(int argc, char *argv[])
{
const char n=8;//相当于char const n=8;
//测试被锁死的n变量对应地址标识的值是否能否被修改
n = 7;
/*
测试结果:
example.c: In function ‘main’:
example.c:13:4: error: assignment of read-only variable ‘n’
n = 7;
结论:当局部变量使用const关键字之后,n变量对应地址标识的会被锁死,无法修改。
*/
printf("n = %hhd\n", n);
return 0;
}
④.修饰指针
/*用于修饰指针*/
2. const int *p1 = NULL; //常量整形指针
3. int *const p2 = NULL; //整形常量指针
4.
5. /*常量整形指针,不能通过这个指针修改它所指向的变量,
6. 指针本身是可变的。
7. 整形变量指针,指针不可修改,但是指向的变量可以修改*/
/*
const char *p1形式可以锁死p1对应的地址标识中的地址标识中的值,无法锁死p1对应的地址标识中的地址标识。
char *const p2形式能够锁死p2对应的地址标识中的地址标识,不能够锁死p2对应的地址标识中的地址标识中的值。
*/
#include <stdio.h>
#define CONTRL_P1 0 //const char *p1形式学习
#define CONTRL_P2 1 //char *const p2形式学习
int main(int argc, char *argv[])
{
char n = 8;//相当于char const n=8;
char m = 10;
const char *p1 = &n;
#if CONTRL_P1
printf("&n--%p;&m---%p;&p1---%p; p1---%p\n", &n, &m, &p1, p1);
#endif
#if CONTRL_P1
p1 = &m;
/*
测试结果: const char *p1形式无法锁死p1对应的地址标识中的地址标识
测试依据:通过p1 = &m;语句执行前后p1对应的地址标识中的地址标识是否发生改变。
*/
printf("p1---%p\n", p1);
#endif
#if CONTRL_P1
*p1 = 9;
/*
测试结果:const char *p1形式能够锁死p1对应的地址标识中的地址标识中的值。
若代码中定义了const char *p1形式,还去编写*p1 = 9;语句则会出现如下错误:
example.c: In function ‘main’:
example.c:18:6: error: assignment of read-only location ‘*p1’
*p1 = 9;
^
测试依据:通过写*p1 = 9;语句在执行x86main文件时是否由出错。
*/
printf("p1---%p\n", p1);
#endif
char *const p2 = &n;
#if CONTRL_P2
printf("&n--%p;&m---%p;&p2---%p; p2---%p\n", &n, &m, &p2, p2);
#endif
#if CONTRL_P2
p2 = &m;
/*
测试结果:char *const p2 形式能够锁死p2对应的地址标识中的地址标识。
若代码中定义了char *const p2 形式,还去编写p2 = &m;语句则会出现如下错误:
example.c: In function ‘main’:
example.c:55:6: error: assignment of read-only variable ‘p2’
p2 = &m;
^
测试依据:通过p2 = &m;语句在执行x86main文件时是否由出错。
*/
printf("p2---%p\n", p2);
#endif
#if CONTRL_P2
*p2 = 8;
/*
测试结果:char *const p2 形式不能够锁死p2对应的地址标识中的地址标识中的值。
测试依据:通过写*p1 = 8;语句在执行x86main文件时是否由出错。
*/
printf("p2---%p\n", p2);
#endif
return 0;
}
递归(n!)
递归函数,是指在定义的时候,直接或间接调用自身的函数。
C语言递归函数设计:
(1)问题模型本身要符合递归模型(递推模型)
(2)问题的解,当递归到一定层次时,答案是显而易见的,且能结束函数。
(3)先明确函数要实现的功能与参数的关系,暂不管功能具体的实现。
(4)呈现第n层与第n-1层的递推关系。