前面学习了c语言的基本语法特性,本节进行更深刻的学习。编程
& | ^ ~ << >>
vim helloworld.c
helloworld.c:vim
#include <stdio.h> int main() { printf("hello,world!\n"); return 0; }
编译的目的:数组
从c语言
.c
源文件变成可执行文件
gcc helloworld.c -o helloworld.out ./helloworld.out
编译的四个步骤:数据结构
.c
文件->.i
文件->.s
文件->.o
文件->可执行文件(可运行)
记忆法: ISO三步走战略。框架
下面咱们来查看预处理中要作的事情:函数
gcc -o helloworld.i helloworld.c -E
-E
表示只让gcc执行预处理。工具
// 查看helloworld.i文件 cat helloworld.i
vim跳到整个文档底部,命令: :$
学习
能够看到代码的底端是咱们的main函数spa
对比一下.i
文件和.c
文件的区别3d
首先:它们都是c的语法。其次.c文件main函数上面是
#include <stdio.h>
而.i
文件中这行代码不见了,变成了上面这些东西。
因此预处理所作的第一件事情就是展开头文件
将#include <stdio.h>
中stdio.h
展开,将未注释的内容直接写入.i文件。
在预处理步骤中,除了展开头文件,还要进行宏替换。
c语言常量分为直接常量和符号常量:
#define 标识符 常量值 (注意:没有分号)
helloMacro.c源代码:
#include <stdio.h> #define R 10 int main() { int a =R; printf("a=%d\n"); printf("hello,world!\n"); return 0; }
gcc -o helloMacro.i helloMacro.c -E
预处理过以后的代码
# 4 "helloworld.c" int main() { int a =10; printf("a=%d\n"); printf("hello,world!\n"); return 0; }
能够看到10是直接当作一个字符串来替换本来的宏定义R。
宏的本质是发生在预处理阶段单纯的字符串替换(宏替换), 在预处理阶段,宏不考虑语法;
示例代码2:vim helloMacro2.c
#include <stdio.h> #define R 10 #define M int main( M){ printf("hello,world!\n"); return 0; }
gcc helloMacro2.c -o helloMacro2.out ./helloMacro2.out
预处理是没有问题的,能够成功的编译执行。宏不考虑C语言的语法。它很单纯,字符串替换。
一般定义数组咱们这样写:
int a[10]; int b[10];
定义两个相同大小的数组,这里咱们就能够改成下面代码。
#define R 10 int a[R]; int b[R];
一次修改,能够修改两份。
宏也是能够传递参数的,能够作一些函数能够作的事情
vim helloMacroFunction.c
源代码:
#include <stdio.h> #define R 10 #define M int main( #define N(n) n*10 M){ int a = R; int b = N(a); printf("b = %d\n",b); printf("a =%d\n",a); printf("hello,world!\n"); return 0; }
gcc helloMacroFunction.c -o helloMacroFunction.out ./helloMacroFunction.out
这里的处理过程: 首先将参数a替换到上面的宏中,上面就变成了N(a) a*10
,以后再用a*10
替换下面的N(a)
int b = N(a); //变成了 int b =a*10;
gcc -o helloMacroFunction.i helloMacroFunction.c -E
预处理以后:
# 8 "hello.c" int main(){ int a = 10; int b =a*10; printf("b = %d\n",b); printf("a =%d\n",a); printf("hello,world!\n"); return 0; }
先不考虑宏实现,先来写一个正常的求和函数。
vim helloAdd.c
#include <stdio.h> #define R 20 #define M int main( #define N(n) n*10 int add(int a,int b){ return a+b; } M){ int a = R; printf("a =%d\n",a); printf("hello,world!\n"); int b =N(a); printf("b = %d\n",b); int c =add(a,b); printf("c =%d\n",c); return 0; }
gcc helloAdd.c -o helloAdd.out ./helloAdd.out
使用宏函数实现求和。
vim helloAddMacro.c
#include <stdio.h> #define R 20 #define M int main( #define N(n) n*10 #define ADD(a,b) a+b int add(int a,int b){ return a+b; } M){ int a = R; printf("a =%d\n",a); printf("hello,world!\n"); int b =N(a); printf("b = %d\n",b); int c =add(a,b); printf("c =%d\n",c); int d =ADD(a,b); printf("d =%d\n",d); return 0; }
gcc helloAddMacro.c -o helloAddMacro.out ./helloAddMacro.out
能够看到使用宏函数和普通函数的求和效果是一致的。结果与简单的字符串替换一致。
ADD(a,b)
被替换成 a+b
所以式子变成int d = a+b;
gcc -o helloAddMacro.i helloAddMacro.c -E vim helloAddMacro.i
版本3,宏定义中优先级问题。
#include <stdio.h> #define R 20 #define M int main( #define N(n) n*10 #define ADD(a,b) a+b int add(int a,int b){ return a+b; } M){ int a = R; printf("a =%d\n",a); printf("hello,world!\n"); int b =N(a); printf("b = %d\n",b); int c =add(a,b); printf("c =%d\n",c); int d =ADD(a,b); printf("d =%d\n",d); int e =ADD(a,b) * ADD(a,b); printf("e =%d\n",e); return 0; }
预测一下e的输出为: a+b*a+b
ab先乘起来,a=20,b=200,ab=4000,而后加上a,b:获得结果(4220)
gcc helloAddMacroPrecedence.c -o helloAddMacroPrecedence.out ./helloAddMacroPrecedence.out
运算是等咱们编译完了,执行的时候才会运行的。预处理阶段不会进行运算操做。
真正运算的时候,会按照运算符号的优先级来进行
解决方案:
#define ADD(a,b) (a+b)
gcc helloAddMacroPrecedence.c -o helloAddMacroPrecedence2.out ./helloAddMacroPrecedence2.out
加个括号,保证优先级更高一点。
宏函数和正常函数的优点?
正常的add函数须要返回值类型,须要传递进来的参数有类型要求。
讲传入的a,b 类型进行改变,如变为两个浮点型数,程序就会自动类型转换。
可是宏函数就没有这种要求能够不用考虑输入值的类型,这与普通的函数定义不一样。
int c =add(10.5,20.4); printf("c =%d\n",c); float d =ADD(10.5,20.4); printf("d =%f\n",d);
gcc helloAddMacroPrecedenceCompare.c -o helloAddMacroPrecedenceCompare.out ./helloAddMacroPrecedenceCompare.out
普通函数例如
int add(int a,int b)
除了在开头要声明值的类型,还要设置返回值,所以在定义过程与调用过程相对复杂。若能用宏定义实现的状况应优先考虑宏定义.
宏是不考虑数据类型,不考虑c语言的语法的。只是简单的字符串的处理。
预处理阶段,除了宏以外,还提供了一个叫作mtianyan:条件编译的功能。
能够按照不一样的条件,编译不一样的程序部分,从而产生不一样的目标代码文件。对于程序的移植和调试都是颇有用的。
下集预告: 和宏比较相近的功能,typedef
严格来说,typedef
和预处理是没多大关系的,放在这里是由于容易搞混淆。
typedef
做用是给一个变量类型起别名。简记: 取别名; 可是属于C语法, 结束要加分号, 与#define
不一样, 它不须要加分号。
typedef int tni;
将int
用tni
代替,在以后的int
定义可直接写为:
tni a;
咱们以前定义指针的状况:
int *p; // 若是咱们加上typedef typedef int *p;
typedef int *p;
是给int *
这个数据类型(还记得大明湖畔咱们说过的,typedef
做用是给一个变量类型起别名)起了别名p
pq=null // 根据上面的typedef等价于 int* q=null
将int类型换成tni:
typedef int tni; M){ tni a = R;
gcc helloAddMacroPrecedenceCompareTypedef.c -o helloAddMacroPrecedenceCompareTypedef.out ./helloAddMacroPrecedenceCompareTypedef.out
能够看到程序能够成功的运行,注意, 真名能够和别名共存。
以前咱们已经试验过了宏是会在预处理阶段被替换的,而tni不会在.i
文件中被替换。
gcc -o helloAddMacroPrecedenceCompareTypedef.i helloAddMacroPrecedenceCompareTypedef.c -E vim helloAddMacroPrecedenceCompareTypedef.i
咱们一般在使用typedef
的时候,一般都是给本身自定义的数据类型起别名。
好比size_t
就是系统给咱们定义的一个类型的别名。
typedef unsigned long size_t; // 定义一个结构体 struct stu{ }; // 使用结构体的时候 struct stu xxx; // 给结构体起别名 typedef struct stu{ } stu_t; // 起了别名以后使用结构体 stu_t xxx;
区别2: 宏定义开头声明,全局任何做用域均可以直接使用。
注意typedef若是写在方法体内,则只可做用于该做用域的花括号内。
下节课: 结构体
以前的学习中,使用的变量类型大可能是一些比较简单的变量类型。
好比:
int a = 0; float b =0.0;
这些变量之间没有什么联系,然而不少时候咱们存储的数据比较复杂,不能经过简单的类型进行描述。
好比咱们须要存储一个武器的信息:
它会包含武器的不少信息。以前的全部数据类型,都不足以描述一个武器的多种信息。
数组也不行,由于数组只能存放同类型的数据,武器的名字和价格类型不一样。
结构体是不一样类型变量的集合 & 数组是相同类型变量的集合
vim Cstructweapon.c
#include <stdio.h> struct weapon{ // 武器名字 char name[20]; // 攻击力 int atk; // 价格 int price; }; int main() { int a =0; float b =0.0; struct weapon weapon_1; return 0; }
struct 结构体类型名{}
,只是建立了一个结构体类型。
可是咱们可使用这一结构体类型struct weapon
来声明变量
struct weapon weapon_1;
这种定义方法是声明和定义分离的形式。
第二种直接在声明时去定义。
struct weapon{ // 武器名字 char name[20]; // 攻击力 int atk; // 价格 int price; }weapon_1;
至关于定义了一个全局变量,变量名为weapon_1,类型是
struct weapon
。简单程序能够运用
第三种方法:
struct{ // 武器名字 char name[20]; // 攻击力 int atk; // 价格 int price; }weapon_1;
这种方法就不能用这个结构体类型去定义其余的变量。匿名结构体类型。
下节课预告: 如何进行结构体的初始化与引用
定义告终构体以后: 咱们要知道如何初始化,以及如何访问结构体成员
在对结构体进行初始化的时候,能够等于一个初始化列表。经过一个花括号将常量括起来,
依次赋值给结构体成员。
struct weapon weapon_1 = {"weapon_name",100,200}; // 访问结构体里的成员,使用点 printf("%s\n,%d\n",weapon_1.name,++weapon_1.price);
#include <stdio.h> struct weapon{ // 武器名字 char name[20]; // 攻击力 int atk; // 价格 int price; }; int main() { int a =0; float b =0.0; struct weapon weapon_1 = {"weapon_name",100,200}; // 访问结构体里的成员,使用点 printf("%s\n%d\n",weapon_1.name,++weapon_1.price); }
gcc Cstructweapon.c -o Cstructweapon.out ./Cstructweapon.out
结构体成员变量能够和普通变量同样进行操做。weapon_1.name
应该被咱们视为一个总体
a++; ++weapon_1.price //能够看出它能够像普通变量同样操做
若是咱们须要十个武器的数据,咱们须要使用数组,结构体数组。
.
(点)运算符是一个成员运算符,在全部运算符中运算级最高,可用.
运算符访问结构体内部成员.结构体数组和普通数组,差异不大。
int a[2]; // 包含两个int类型的元素 //结构体数组里包含两个结构体类型的元素,每一个元素有结构体里的三个成员 struct weapon weapon_2[2]; // 每一个元素都是一个结构体类型的数据
vim CstructweaponArray.c(在原基础上添加代码)
struct weapon weapon_2[2]={{"weapon_name1",50,100},{"weapon_name2",99,200}}; printf("%s\n%d\n",weapon_2[0].name,weapon_2[1].atk);
gcc CstructweaponArray.c -o CstructweaponArray.out ./CstructweaponArray.out
两种结构体数组的初始化方式:
// 直观式 struct weapon weapon_2[2]={{"weapon_name1",50,100},{"weapon_name2",99,200}}; // 通常式 struct weapon weapon_2[2]={"weapon_name1",50,100,"weapon_name2",99,200};
两种方式均可以成功的初始化。
了解: 什么是指向结构体变量的指针变量,以及如何去使用这个变量。
struct weapon *w ; //定义一个指向weapon的w结构体指针 w=&weapon_1; //具体指向weapon_1的内存地址 (*w).name //左右两侧的括号不可省略,由于.运算符优先级高于星 w->name // 箭头叫作指向运算符 weapon_1.name //这3种访问类型都是同样效果
vim CstructweaponArrayPointer.c
struct weapon * w; w = &weapon_1; printf("---------------------------------\n"); printf("name=%s\nname=%s\nname=%s\n",(*w).name,w->name,weapon_1.name);
gcc CstructweaponArrayPointer.c -o CstructweaponArrayPointer.out ./CstructweaponArrayPointer.out
cp CstructweaponArrayPointer.c CstructweaponArrayPointer2.c vim CstructweaponArrayPointer2.c
结构体数组指针,不用取地址符&
数组的名字表明了这个数组的内存首地址, 数组括号内的长度表明了数组的单元数,数据类型是int的话就按照int类型(32位系统上是4个字节)乘以单元数的长度,若是数据类型是结构体的话就按照结构体的长度乘以单元的长度。
p++,不是内存位置右移了一个字节,而是右移了一个单位长度的结构体weapon的内存长度。因此就不难理解为何右移到了第二个结构体实例的首地址上了。
struct weapon weapon_2[2]={{"weapon_name1",50,100},{"weapon_name2",99,200}}; struct weapon *p; p = weapon_2; //数组的名字对应着数组第一个元素的起始地址 // 取成员值: p->name 至关于 weapon_2[0].name printf("%s\n",p->name); printf("---------------------------------\n"); p++; // 等价于 weapon_2+1 让其指向weapon_2[1] printf("%s\n",p->name);
gcc CstructweaponArrayPointer2.c -o CstructweaponArrayPointer2.out ./CstructweaponArrayPointer2.out
公用体类型也被称之为联合体,建立公用体类型可使用关键字union
理解起来很简单,让几个不一样类型的变量共享同一块内存地址
vim union.c
#include <stdio.h> union data{ int a; char b; int c; }; // 表明a,b,c会只在同一个内存空间。 int main() { union data data_1; data_1.b ='C'; data_1.a =10; // 后面被赋值的成员才是真正起做用的成员 return 0; }
优势是节省一部分的开销。缺点: 同一时刻只能存储一个成员。
好比,对b进行赋值,对a进行赋值。以最后一个赋值为准,由于全部成员共享一块内存。
(后赋值的会把前面的覆盖掉)
共用体类型的长度是全部成员中最长的那个成员的长度。好比上面的例子中int有四个字节,char只有一个字节。int就是最长的,所以这个共用体类型就占四个字节。
union data data_1 = {10};
所以在对公用体或联合体赋值的时候,只能给一个变量。
对于结构体来讲,这个是不同的
struct data{ int a; char b; int c; };
可能会下意识的觉得struct占用的内存空间大小是这几个成员变量的大小之和。
4+1+4=9
结构体的内存大小占用,涉及字节对齐,是一种计算机用空间换取时间的方式。
大小 = 最后一个成员的偏移量+最后一个成员的大小+(可选)末尾填充字节数。
偏移量就是某一个成员的实际地址和结构体首地址之间的距离。(偏移量,实际地址和结构体首地址之间的距离)
因此整个占用12。
例子中最宽的是int,4能够被12整除。 若是不能,c以后也要填充末尾填充字节数。
union共用体是多种不一样数据类型的合集,所占字节按照共用体里面所占内存空间最大的类型决定。
例若有char(1字节)型和int(四字节)型,按照int型来计,整个共用体所占四个字节;
此外,共同体里面全部数据的地址是相同的,这个决定了它在有多种数据类型的时候,有且只能存放这些数据类型里面的一种数据。以最后一次赋值为准。
struct结构体也是不一样数据类型的合集,所占字节按照里面全部数据类型的长度来决定.
例如:char(1字节)和int(4字节),struct所占空间是8个字节,不是5个字节,缘由是涉及到字节对齐,字节对齐是为了方便计算机快速的读取数据。
vim struct_size.c
#include <stdio.h> struct data{ int a; char b; int c; }; int main(){ // union data data_1; //data_1.b ='C'; // data_1.a =10; printf("%lu\n",sizeof(struct data)); return 0; }
gcc struct_size.c -o struct_size.out
这里的12是这样计算来的,a是第一个成员,偏移量为0.b偏移量为4,b自身大小为1,能够整除,不用补字节,c偏移量为5,自身大小为4,字节对齐为8,加上自身大小4,12了。
而后判断12是否是结构体中最宽的基本类型成员的整数倍。
%p
表示输出这个指针%d
表示后面的输出类型为有符号的10进制整形,%u
表示无符号10进制整型,%lu
表示输出无符号长整型整数(long unsigned)vim union_address.c
#include <stdio.h> union data{ int a; char b; int c; }; // 表明a,b,c会只在同一个内存空间。 int main() { union data data_1; data_1.b ='C'; data_1.a =10; printf("%p\n%p\n%p\n",&data_1.a,&data_1.b,&data_1.c); return 0; }
gcc union_address.c -o union_address.out ./union_address.out
共用体中成员变量的地址所有相同。
(以前使用到的都是)静态数据结构:
以后程序运行的时候,空间的位置和容量都不会再改变了。
好比咱们在使用数组的时候,数组的长度就必须事先定义好。可是不少时候咱们不知道。
须要一个能够动态存储分配的数据结构。也就是可变大小的数据结构。
链表:
指向为空时链表结束。 在链表里访问某一个元素,必须经过上一个元素提供的下一个元素的地址才行。
想访问B元素,得找到a元素中存放的b元素的地址才能访问到b元素。
静态链表:
(全部节点都是在程序中定义的,而不是临时开辟的)
【由三个武器信息的节点组成,因此用结构体类型做为节点元素】
vim struct_weapon_link.c
#include <stdio.h> struct weapon{ int price; int atk; // 节点的数据部分 struct weapon * next; // 下一节点的地址 }; int main() { struct weapon a,b,c,*head; // 除过三个武器,还要定义一个头指针 a.price =100; a.atk = 100; b.price =200; b.atk =200; c.price =300; c.atk =300; // 链接成链表 head = &a; a.next =&b; b.next =&c; c.next = NULL; struct weapon *p; p =head; while(p!=NULL){ printf("%d,%d\n",p->atk,p->price); p=p->next; } }
gcc struct_weapon_link.c -o struct_weapon_link.out ./struct_weapon_link.out
静态链表,全部的节点都是在程序中去定义的,而不是临时开辟的。
链表有一个头指针和尾指针,每一个指针指向的是链表下一个数据的地址。
在结构体里面加入指针就构成链表,此时指针结构体包括两个部分,一个是信息域(数据域),另外一个是指针域。
链表:能够用malloc
来动态分配所需的内存,而且须要用free
手动释放在堆里面申请的内存。
程序执行过程当中从无到有的创建起一个链表,也就是说须要一个一个的开辟新节点,输入新节点的数据,而后创建起先后相连的关系。
创建武器信息的单向动态链表:
vim dynamic_linked_list.c
#include <stdio.h> #include <malloc.h> struct weapon{ int price; int atk; struct weapon * next; }; // 须要一个建立链表的函数,返回值是链表的头指针,返回类型是struct weapon * struct weapon * create(){ struct weapon *head; struct weapon *p1,*p2; // 3个指针都用来指向struct weapon类型数据,p1,p2分别指向当前新建立的节点和上一个节点。 int n=0;// 临时变量,记录当前链表中节点个数 // malloc分配内存块的函数,sizeof判断数据类型长度符 // (int)malloc(16) 分配16个int型内存块 p1=p2=(struct weapon*)malloc(sizeof(struct weapon)); // 从键盘输入数据,给第一个节点。 scanf("%d,%d",&p1->price,&p1->atk); head = NULL;// 一开始链表不存在,置空 while(p1->price!=0){ // 约定price为0时中止输入 n++; if(n==1) head=p1; else p2->next=p1; p2=p1; // 保留p1当前所指向的的地址,保留上一个节点。 //须要开辟一个新的动态存储区,把这个的地址载给p1 p1=(struct weapon*)malloc(sizeof(struct weapon)); scanf("%d,%d",&p1->price,&p1->atk);//开辟后输入数据 } p2->next=NULL; return (head); }//p1,p2一个用来指向链表新创立的节点,一个用来指向下一个节点 int main() { struct weapon *p; p=create(); // p成为链表的头指针 printf("%d,%d\n",p->price,p->atk);//打印第一个节点的信息 return 0; }
gcc dynamic_linked_list.c -o dynamic_linked_list.out ./dynamic_linked_list.out
while(p!=NULL){ printf("%d,%d\n",p->atk,p->price); p=p->next; }
如今的框架中:一般与加减运算快相同,比乘除法快不少
六种位运算符
右移
vim and_operation.c
#include <stdio.h> int main() { // & | ^ ~ << >> int a = 4; //0100 int占用4字节,32位补码。 int b = 7; //0111 int c = a&b; //0100 逻辑与运算 printf("%d\n",c); return 0; }
gcc and_operation.c -o and_operation.out ./and_operation.out
按位与的应用
任何一个数和0作按位与结果必定是0
vim odd_even.c
#include <stdio.h> int main() { // & | ^ ~ << >> int a =4;//0100 int b =7;//0111 int c =a&1;//0100 int d =b&1;//0100 printf("%d\n",c); printf("%d\n",d); return 0; }
输出为0,1; 输出为0是偶数,输出为1是奇数。
由于除过最后一位前面的都必定是2的倍数了。最后一位为1,表示是奇数。
最后一位为0,表示是偶数。
gcc odd_even.c -o odd_even.out ./odd_even.out
将参与运算的两个数据按位进行逻辑或(有一个1的时候结果就是1)运算
vim bit_or.c
#include <stdio.h> int main() { // & | ^ ~ << >> int a =9; //1001 int b =5; //0101 int c =a|b;//1101 printf("%d\n",c); return 0; }
gcc bit_or.c -o bit_or.out ./bit_or.out
输出为1101(13)
按位或的做用:
设定数据的指定位,与255(0xFF)作按位或运算
好比咱们想让9低八位的数据位设置为1.
a = a | 0xFF;
设定数据a的指定二进制数后8位置为1
vim bit_or_8.c
#include <stdio.h> int main() { // & | ^ ~ << >> int a =9; //1001 int b =5; //0101 int c =a|b;//1101 printf("%d\n",c); int a =a|0xff; printf("%d\n",a); return 0; }
gcc bit_or_8.c -o bit_or_8.out ./bit_or_8.out
^
将参与运算的两个数据按对应的二进制数逐位进行逻辑异或运算.只有对应位结果互斥,结果才为真。
vim bitwise_xor.c
#include <stdio.h> int main() { // & | ^ ~ << >> int a =9; //1001 int b =5; //0101 int c =a^b;//1100 printf("%d\n",c); return 0; }
gcc bitwise_xor.c -o bitwise_xor.out ./bitwise_xor.out
a^0xff;
a= a^b; b= b^a; a= a^b;
vim bitwise_xor_change.c
#include <stdio.h> int main() { // & | ^ ~ << >> int a =9; //1001 int b =5; //0101 a= a^b; b= b^a; a= a^b; printf("%d\n%d\n",a,b); return 0; }
gcc bitwise_xor_change.c -o bitwise_xor_change.out ./bitwise_xor_change.out
成功的将a,b进行了调换
涉及到的运算原理能够具体以下阐述:(下面的= 是等于的意思 而非赋值号)
P0. x^x=0 P1. a^0=a P2. c=a^x --> a=c^x (a=a^x^x=a^0=a)
故有交换 解释:
a^=b; 起先 a被赋值为 a^b
b^=a; 此时 b被赋值成最开始的a值,而a保持上一步骤中的结果值不变
a^=b; 此时 a被赋值成最开始的b值,而b保持上一步骤中的结果值不变
惟一的一个单目运算符,右结合性。把零换成1,1换成0
~1000 = 0111
左移:将数据对应的二进制值逐位左移若干位;
高位会被舍弃掉,低位补零; 至关于乘以2的n次方
左移须要注意的是int是一个有符号的类型,移动过程当中把符号位移出去了会溢出
右移:将数据对应的二进制值逐位右移若干位
mtianyan: 低位丢弃,无符号数补零,有符号数,高位补0或1(根据符号位判断,就是正数数补0,负数补1); 至关于除以2的n次方。
vim left_right_shift.c
#include <stdio.h> int main() { // & | ^ ~ << >> int a =3; //0000 0011 a = a<<4; //0011 0000 32+16=48 printf("a=%d\n",a); int i =1; // 0000 0001 i = i<<33; // 至关于i<<1 printf("i=%d\n",i); // 当移位位数超过该数值类型的最大位数时,编译器会用移位位数去模该类型位数,而后按照余数进行移位。 int b = 4; b = b>>1; printf("b=%d\n",b); return 0; }
gcc left_right_shift.c -o left_right_shift.out ./left_right_shift.out
能够看到会报出warning
递归调用就是在调用函数的过程当中,被调用的函数调用它自己的过程。
递归调用有时候会牺牲效率
vim recursiv_call.c
#include <stdio.h> void func(){ printf("1\n"); func(); } int main() { func(); return 0; }
gcc recursiv_call.c -o recursiv_call.out ./recursiv_call.out
无尽地输出1,而后爆出段错误,核心已转储
所以咱们的递归是要加上条件的。适当的时候选择是否递归,由于递归在某些时候会牺牲效率。
使用递归求阶乘
vim recursiv_call_factorial.c
#include <stdio.h> int func(int n) { int r =0; if(n<0){ printf("error"); }else if(n==0 || n==1){ return 1; }else{ r =n *func(n-1); return r; } } int main() { int n =0; printf("please input the num:"); scanf("%d",&n); // 使用func求阶乘 int result = func(n); printf("the result is %d\n",r); return 0; }
gcc recursiv_call_factorial.c -o recursiv_call_factorial.out ./recursiv_call_factorial.out
注意这里递归的整型溢出
递归是一种编程技巧,函数调用过程当中函数体内又调用了它本身
函数调用的过程:
函数A调用函数B,main函数中调用A,A中调用了B,B有两个参数,a和b被称为形参。
函数没有被调用的时候,形参是不会被分配内存单元的。在A中调用了B,A就被称为主调函数。
主调函数中调用一个函数的时候,这个函数里的参数被称为实参。
函数调用所须要作的第一件事:
为被调用的函数(B)的形参分配一个临时的内存单元而后才能把两个对应的实参的值传递进来。
同时还须要传递的是主调函数(A)的返回地址。(俗称保护现场)
之因此要把A的返回地址保存起来,是由于被调函数(B)执行完了还须要继续执行主调函数(A)后面的代码。
经过return语句将函数计算后的值带回到主调函数。
保护现场时传递的返回地址,参数,函数调用结束的返回值。这些数据都保存在栈中。
递归是调用自身,但自身也是函数调用,本质就是函数的调用。
求阶乘的函数,因为递归也是符合函数调用原则的,因此全部被调用的函数都会建立一个副本,为各自的调用者服务,不受其余函数影响。递归函数被调用多少次,就会建立多少个它自身的副本。
这个副本就好像是一个新的函数同样。系统会用栈来管理它的内存。它是一个独立的存在,彻底能够理解为这已是一个新的func了。它被分配了独立的内存单元。
递归:大规模——>化简——>小规模,直到问题可求。
递归函数同时必须有 递归条件和递归表达式,不然会进入死循环。
递推(for):则是由小问题的解逐步代入大问题并求出解。