导图社区 C语言
什么是C语言?这张图介绍了C语言的使用及其常用选项介绍、特点、结构、函数库、以及嵌入式的编写技巧,如果你想学习C语言的话,赶紧来这里了解一下这张思维导图吧,看看能不能再一定程度上帮你解决一些问题。
编辑于2020-02-13 05:43:49C语言
编写代码的方法 利用函数返回值确定错误的位置 利用标志信号作为线索 决定函数最后的返回值 定义的数据类型 在有些例程中 和常量一样全部大写  名字只是一个代号 我们真正关注的是名字背后的数据内容
2-1 :GCC的使用及其常用选项介绍
【重点】 掌握C语言如何变成机器指令的过程 gcc工具的几个常用选项的意义 【难点】 C编译过程中在gcc工具上的体现 【实验考核】 自己编写程序,利用gcc工具集验证每一步的执行效果 举例说明gcc选项的意义: gcc -I gcc -D gcc -E gcc -S gcc -c
Gcc概述
GCC最初的全名是GNU C Compiler 随着GCC支持的语言越来越多,它的名称变成了GNU Compiler Collection 翻译官 翻译组织 文件后缀名 gcc .c gcc -o output gcc -o 输出的文件名 输入文件名 gcc -v -o
C语言常见错误举例
预处理错误: #include "name" #include not find gcc -I跟查找头文件的目录 编译错误: 语法错误 ; { } 链接错误: 原材料不够 undefined reference to `fun' 寻找标签是否实现了,链接时是否加入一起链接 多了 multiple definition of `fun' 多次实现了标签,只保留一个标签实现
一、概述
计算机语言
高级语言
世界上第一个高级语言是FORTRAN(??应该是Basic),后来陆续出现很多种高级语言,目前广泛应用的高级语言有C/C++、Java和C#等。
翻译程序
翻译程序通常有两种工作方式,即编译方式和解释方式。 编译方式是将源程序全部翻译为二进制程序后再执行,再此完成翻译工作的程序称为“编译程序”,编译后的二进制程序称为“目标程序”; 解释方式是翻译一句执行一句,边解释边执行,在此完成翻译工作的程序称为“解释程序”。
源程序
源程序是指用___高级语言____编写的程序,它必须翻译___目标程序____才能够执行 使用高级语言编写的程序称为“源程序”,源程序无法在计算机上直接运行, 必须将其翻译为二进制程序后才能执行
汇编程序
汇编程序将___汇编语言___编写的程序转换为目标程序 在汇编阶段,汇编程序对汇编语言源程序进行汇编,生成一个可重定位的目标文件
c语言的特点
c++的特点
C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点。
优点
(1)出色的移植性,能在多种不同体系结构的软/硬平台上运行(修改量越小,移植性越好); (2)简洁紧凑,使用灵活的语法机制,并能直接访问硬件(效率高); (3)很高的运行效率
c++面向对象
封装 抽象 继承 多态
C语言的标准
K&RC、C89、C99、C11
指针与数组的区别
1.指针和数组的区别: 指针p 是个变量 存储着一个地址 可以指向任意的地址变量指针p 是个变量 存储着一个地址 可以指向任意的地址 数组a 是个常量 代表开头地址 不能改变常量数组a 是个常量 代表开头地址 不能改变 int a[100]; 2.数组和指针是一回事 p[0]=*p 数组也是内存分配的一种形式
C语言的编译过程
预处理 cpp -o a.i 001.c 替换 【gcc -E】 编译 /usr/lib/gcc/i486-linux-gnu/4.4.3/cc1 -o a.s 001.c 【gcc -S】 汇编 as -o a.o a.s 【gcc -c】 链接 /usr/lib/gcc/i486-linux-gnu/4.4.3/collect2 -o build a.o+... 【gcc -o】 gcc -o build 001.c 【问题】 define include是关键字吗?
编译器的识别过程
1.先找变量名------a 2.在确定数据结构-----数组 指针 函数? 2.编译器找右侧的修饰符有 ---- a[10] 那就是数组 a(int)就是函数 3.编译器再看左侧*--之前右侧没有 -*a那就是指针 之前有 *a[10] 就是存放指针 的数组 *a(int) 就是返回指针类型 的函数 **a 就是指向指针 的指针 4.再看左侧 -----------------------int *a[10] 就是存放指向int类型指针 的数组 int *a(int) 就是返回int类型指针 的函数 int **a 就是指向指向int类型 的指针
1.先找变量名
2.在确定数据结构-----数组 指针 函数
2.编译器找右侧的修饰符有
3.编译器再看左侧*--之前右侧没有 -*a那就是指针
4.再看左侧
c语言结构
预定义
预处理#ifnodefi
#include 包含头文件 #define 宏 替换,不进行语法检查 #define 宏名 宏体 加括号 #define ABC 5+3 printf("the %d\n",ABC*5); 5+3*5 #define ABC (5+3) #define ABC(x) (5+(x)) #ifdef #else #endif 预定义宏 __FUNCTION__ : 函数名 __LINE__ : 行号 __FILE__ : 文件名
条件预处理举例
调试版本 发行版本 gcc -D : gcc -DABC1 === #define ABC1
宏展开下的#、##
# 字符串化 ## 连接符号 #define ABC(x) #x #define ABC(x) day##x
宏定义#define
1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) #define 宏名 宏体 宏名 : 大写字母表示 //#define SECOND_OF_YEAR 123456 #define SECOND_OF_YEAR (365*24*3600)UL 8bit : 0 - 255 256 16bit: 0 - 65535 INT 的常量 255 256 365*24*3600
类型定义typedef

关键字
编译器 预先定义了一定意义的 字符串预先定义了一定意义的编译器 预先定义了一定意义的 字符串 int a; 32个关键字
类型修饰符
对内存资源存放位置的限定 资源属性中位置的限定
auto
默认情况--------> 分配的内存可读可写的区域分配的内存可读可写的区域 auto int a; auto long b; 区域如果再{ },栈空间
register寄存器
auto int a; register int a; 限制变量定义在寄存器上的修饰符 定义一些快速访问的变量 编译器会尽量的安排CPU的寄存器去存放这个A,如果寄存器不足时,a还是放在存储器中。 &这个符号对register不起作用 --------------------------- 内存(存储器) 寄存器 0x100 R0,R2
static本地
静态 应用场景:修饰3种数据: 1)函数内部的变量)函数内部的变量 int fun() { int a; ===> static int a; } 2) 函数外部的变量函数外部的变量 int a; ====> static int a; int fun() { } 3)、函数的修饰符)、函数的修饰符 int fun(); ===> static int fun();
const只读
常量的定义 只读的变量 const int a = 100; a = 200;
extern外部
外部申明 这个关键字真的比较可恶, 在定义变量的时候,这个extern居然可以被省略(定义时,默认均省略);int a 在声明变量的时候,这个extern必须添加在变量前, extern int a 所以有时会让你搞不清楚到底是声明还是定义。 或者说,变量前有extern不一定就是声明,而变量前无extern就只能是定义。 注: 定义要为变量分配内存空间; 而声明不需要为变量分配内存空间。
对变量而言
总结: 对变量而言,如果你想在本源文件(例如文件名A)中使用另一个源文件(例如文件名B)的变量, 方法有2种: (1)在A文件中必须用extern声明在B文件中定义的变量(当然是全局变量);  (2)在A文件中添加B文件对应的头文件,当然这个头文件包含B文件中的变量声明,也即在这个头文件中必须用extern声明该变量,  否则,该变量又被定义一次。
对函数而言
对函数而言,如果你想在本源文件(例如文件名A)中使用另一个源文件(例如文件名B)的函数,方法有2种:(1)在A文件中用extern声明在B文件中定义的函数(其实,也可省略extern,只需在A文件中出现B文件定义函数原型即可);(2)在A文件中添加B文件对应的头文件,当然这个头文件包含B文件中的函数原型,在头文件中函数可以不用加extern。
volatile不优化编译
告知编译器编译方法的关键字,不优化编译不优化编译 修饰变量的值的修改,不仅仅可以通过软件,也可以通过其他方式(硬件外部的用户) int a = 100; while( a==100 ); mylcd(); ----------------------- [a] : a的地址 f1: LDR R0,[a] f2: CMP R0,#100 f3: JMPeq f1 ----> JMPEQ f2 f4: mylcd();
Inline频繁调用
在C++中,关键字Inline可以被加入到任何函数的声明中。 这个关键字请求编译器用函数内部的代码替换所有对于指出的函数的调用。 这样做在两个方面快于函数调用。 这样做在两个方面快于函数调用: 第一,省去了调用指令需要的执行时间; 第二,省去了传递变元和传递过程需要的时间。 但是使用这种方法在优化程序速度的同时, 程序长度变大了,因此需要更多的ROM。 使用这种优化在Inline函数频繁调用并且只包含几行代码的时候是最有效的。
自定义数据类型
C编译器默认定义的内存分配不符合实际资源的形式 自定义 = 基本元素的集合
struct
元素之间的和 struct myabc{ unsigned int a; unsigned int b; unsigned int c; unsigned int d; }; int i; struct myabc mybuf; ------------------------------ 顺序有要求的
union
共用起始地址的一段内存 技巧型代码 union myabc{ char a; int b; }; union myabc abc; int a;
enum
enumerate 一一列举 被命名的整型常数的集合 #define MON 0 #define TUE 1 #define WED 2 enum abc{ MOD = 0,TUE,WED } enum 枚举名称{ 常量列表 }; enum week{ Monday = 0 ,Tuesday =1 ,Wednesday = 2, Thursday,Friday, Saturday,Sunday };
typedef
数据类型的别名 int a =170; int b = 3600; len_t a = 170; time_t b = 3600; int a; a是一个int类型的变量 typedef int a_t; a是一个int类型的外号 a_t mysize; xxx_t : typedef len_t time_t
杂项
return
返回的概念
sizeof
int a; printf("the a is %d\n",sizeof(a)); sizeof : 编译器给我们查看内存空间容量的一个工具
常运算符操作
【重点】 掌握C语言的常用关键字及其应用场景,使用技巧 掌握位运算的典型操作 掌握常用的逻辑操作 【难点】 when to do? how to do? why to do? 【实验考核】
算术操作运算
+ 、-
A + B
* 、/、%
* / CPU int a = b*10; CPU可能多个周期,甚至要利用软件的模拟方法去实现乘法 int a = b+10; CPU 一个周期可以处理 %: 0 % 3 = 0 1%3 = 1 2%3=2 3%3=0 4%3=1 ... ... n % m = res [0 - m-1] 取一个范围的数: eg. 给一个任意的数字,得到一个1到100以内的数字? (m% 100)+1 ===> res; 得到M进制的一个个位数 循环数据结构的下标
逻辑运算
真 假 返回结果就是1 0 int a = -1; if(a)
|| 、&&
A || B === B || A != A && B
>、>=、<、<=
!
对比位运算中取反 int a = 0x0; !a if(!a){ } ~a 0xffff
? :
位运算
<< 、>>
左移 : 乘法 *2 二进制下的移位 m m 4: 0 0 1 0 0 8: 0 1 0 0 0 int a = b*32; ===> b [数据、数字] -1 *2 -2: 8bit 1 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 ==== -1 1 1 1 1 1 1 1 0 右移:除以2 m >> n m/2^n 符号变量有关 int a; a>>n unsigned int a a>>n int a = xxx; while(a){ a = a>>1; } printf("-------------\n");
&、|
& 、 | A & 0 ----> 0 &: 屏蔽 int a = 0x1234; a & 0xff00; 屏蔽低8bit,取出高8bit A & 1 ---> A &:取出 &:清零器 clr | : A | 0 === A 保留 A | 1 === 1 设置为高电平的方法,设置set 设置一个资源的bit5为高电平,其他位不变 int a; a = (a | (0x1 a | (0x1 清除第五位 int a; a = a & 0 1 1 1 1 1 31 a & 31 31 : 32bit ~(0x1 a = a & ~(0x1 a = a & (~(0x1 我们想资源456bit设置为101?
^、~
^ : 1 ^ 1 = 0 0 ^ 0 = 0 1 ^ 0 = 1 算法 AES SHA1 ------------------------------- int fun() { int a = 20; int b = 30; int c; c = a; xxxx-----> a = b; b = c; a = a ^ b; b = a ^ b; a = a ^ b; a = 30 b =20 } ~ 0xf0 ~ 0xffff ff0f
赋值运算
a | (0x1 ~a a = a + b a+=b a = a+b; a+=b a | = (0x1 a &= ~(0x1
=
+=、-=、&=、...
指针运算符
地址++
++、--、+、-
int a = 100; a+1 int *p = xxx [0x12] p+1 [0x12 + 1*(sizeof(*p))] 指针的加法、减法运算,实际上加的是一个单位,单位的大小可以使用sizeof(p[0]) int *p p+1 char *p p+1 p++ p-- : 更新地址
[]
变量名[ n ] n:ID 标签 地址内容的标签访问方式 取出标签里的内存值
逻辑操作符
>= int *p1; int *p2; p1 > p2 *p1 > *p2 --------- == != 1、跟一个特殊值进行比较 0x0 : 地址的无效值,结束标志 if( p == 0x0) NULL 2、指针必须是同类型的比较才有意义 char * 2、指针必须是同类型的比较才有意义 char * int *
逻辑结构
CPU顺序执行程序 PC 分支 ---》 选择 循环
if、else
条件 if(条件表达式) xxx; else yyy;
switch、case、default
多分支 swtich(整形数字) float a; switch(a){ case 1.0: break; case 2.0: }
do、while、for
for : 次数 while : 条件 do
continue、break、goto
goto
数据变量

void
void a; a++; void fun();
num
int编译器决定
大小: 根据编译器来决定 TC2.0 编译器最优的处理大小: 系统一个周期,所能接受的最大处理单位,int 32bit 4B int 16bit 2B int int a; char a; ============================== 整型常量 char a = 300; 300l 300L 2B 65535 int a = 66535; 进制表示 10 十进制 八进制 十六进制 二进制 3bit 8进制 111 0x7 1000 0x8 int a = 010; //8 12 001 010 4bit 16进制 int a = 0x10 //16
long、short
特殊长度的限制符
float、double
大小 float 4B double 8B 内存存在形式 0x10 16 0001 0000 16 1.0 0001 0000 (X) 浮点型常量 1.0 1.1 double 1.0f float
unsigned、signed
无符号 : 数据 有符号 : 数字 内存空间的最高字节 是符号位 还是数据 unsigned int a; char a; >> char a = -1; 0xff a>>1 unsigend char b= -1; b>>1
char
硬件芯片操作的最小单位: bit 1 0 软件操作的最小单位: 8bit == 1B 4M 4Mbit Kbit/s KB/S char a; bit a; 应用场景: 硬件处理的最小单位 char buf[xx]; int buf[x]; ASCII码表 8bit 1 2 2 4 3 8 10 ?? 8bit == 256 char a = 300; a++ 301
string
字符串常量
因为字符串常量的值,实际上表示的是存储这些字符的内存空间的地址,内存空间的地址因为字符串常量的值,实际上表示的是存储这些字符的内存空间的地址, 更准确地说是字符串常量中第 1 个字符的地址,而不是这些字符本身
字符串=存储字符+/0的数组
1.char CharArray[6]={'h','e','l','l','o','\0'}; 这其实就是一个c风格的字符串,c风格的字符串, 字符串的重要属性,结尾一定有个'\0' 2.方式2、char p2[] = "Another Hello";方式2.方式2、char p2[] = "Another Hello";、2.方式2、char p2[] = "Another Hello";
bool
指针
 内存类型资源地址、门牌号的代名词 指针变量 : 存放指针这个概念的盒子 指针 int a; int *p; char *p;
指针变量大小
1、分配一个盒子,盒子要多大? 在32bit系统中,指针就4个字节 指针变量的大小 取决于计算机的地址 取决于计算机的
指针存放的地址
地址
多级指针--存放地址的地址空间
int **p; 存放地址的地址空间 char **p; p[0] p[1] ... p[n] p[m] == NULL ----> 结束了
数组
定义语句: 数据类型 数组名[m] int a[100]; m 的作用域是在申请的时候(m在申请的时候使用 用一下就没了) 1.定义一个空间: 1、大小 2、读取方式 3.越界 a[m] m只在申请的时候用一下 之后不再使用 +-数都可以 a[10] a是一个常量地址 a[-10]
指针数组
int a[100] char * a[100]; sizeof(a) = 100 * 4; int b; sizeof(b) char **a;
字符数组
存储字符的数组 char CharArray[5]={'h','e','l','l','o'}; 存储字符的一个数组
多维数组
线性 只是针对人和人交流的方便些来实现,对于二位数组空间如何在内存的表现形式上,我们不考虑 int abc[3][5]; 3*5*sizeof(int) abc[1] : 空间,地址 abc[1][2]
结构体
将多个变量进行打包处理 进行传递
字节对齐
char a; int b; 1 + 4 = 5 效率,希望牺牲一点空间换取时间的效率 最终结构体的大小一定是4的倍数 结构体里成员变量的顺序不一致,也会影响到他的大小
位域
函数
C语言使用
预定义的使用
使用typedef替代define
当然有时候你无法避免define,但是typedef更好 
逻辑的使用
switch-case语句
每一个由机器语言实现的测试和跳转仅仅是为了决定下一步要做什么,就浪费了处理器时间。 为了提高速度,可以把具体的情况按照它们发生的相对频率排序。 即把最可能发生的情况放在第一,最不可能发生的情况放在最后,这样会减少平均的代码执行时间。当switch语句中的case标号很多时,为了减少比较的次数,明智的做法是把大switch语句转为嵌套switch语句。把发生频率高的case标号放在一个switch语句中,并且是嵌套switch语句的最外层,发生相对频率相对低的case标号放在另一个switch语句中。比如,下面的程序段把相对发生频率低的情况放在缺省的case标号内
2.内存访问与修改
基本数据类型
内存对齐和填充
 假设一个字符需要1个字节,short占用2个字节和int需要4字节的内存。 起初,我们会认为上面定义的结构是相同的,因此占据相同数量的内存。 然而str_1占用12个字节,第二个结构只需要8个字节 在第一个结构,3个不同的4个字节被分配到三种数据类型, 而在第二个结构的前4个自己char和short可以被采用,int可以采纳在第二个的4个字节边界(一共8个字节)
内存的访问
依靠于内存访问符号
地址出发
1.内存访问修饰符*p
void fun(char *p); const char *p : 只读空间,为了空间看看 char *p : 该空间可能修改 strcpy(); sprintf(); void fun(const char *p) { // p[n] = '1'; }
const char *p :只读空间,为了空间看看
char *p : 该空间可能修改
2.数组的读取方法a[1]
数组 内存访问的ID符号 a[1] a[2]
字符串的访问
char *p = "abc"; p[2] = 'e'; 通过修改地址的指向, 抛弃原本的字符串常量, 再创造一个新的字符串, 指向新的字符串
名字出发
结构体的读取方法
整体读取{ }
函数体的限制符 struct abc{xxxxx};
部分的读取->、.
-> .
内容的访问
函数的读取方法fun()
限制符 (a+b) *c 函数访问 int fun(); fun();
内存的修改
位判断
判断某位为0
判断某位是否为0(屏蔽掉不想要的位) a&(0x00000001)
位操作
嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码, 第一个设置a的bit 3 , set 第二个清除a 的bit 3。 clear 在以上两个操作中,要保持其它位不变。 unsigned int a; a |= (0x1 a &= ~(0x1 有符号数:原码、补码、反码的第一位是0表示正数,1表示负数 有符号数和无符号数比较 会自动转化成 无符号数
置位
置位 a|=(1
清零
清零 a&=~(1 a &=~ ((1
变量的使用
变量的功能 控制 数据 状态
1.变量的声明定义
用变量a给出下面的定义 a) 一个整型数(An integer) int a; b)一个指向整型数的指针( A pointer to an integer) int *a; c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an integer) int* *a; int **a; d)一个有10个整型数的数组( An array of 10 integers) int a[10]; e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers) int* a[10]; f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers) int [10] *a ---> int (*a)[10] g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer) int fun(int) *a ---> int (*a)(int) int *fun(int) int *fun(int)返回一个连线地址空间 h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer ) int (*a[10])(int)
1.修饰符使用
static的作用
关键字static的作用是什么? 1、修饰局部变量 默认局部变量 在 栈空间存在,生存期比较短 局部静态化,局部变量 在 静态数据段保存,生存期非常长 2、修饰全局变量 防止重命名,限制变量名只在本文件内起作用 3、修饰全局函数 防止重命名,限制该函数只在本文件内起作用
关键字const
关键字const有什么含意? 比如越界 指针 都可以修改 C :只读,建议性 不具备强制性 != 常量 const int a = 100; c++ : 常量 内存属性: 1、内存操作的大小 2、内存的变化性,可写可读 char *p; const char *p; 【T】 字符串 “ hello world” "aaa" char const *p; char * const p; 【T】 硬件资源 LCD char *p const; const char * const p ROM
关键字volatile防止优化
关键字volatile有什么含意?并给出三个不同的例子。 防止C语言编译器的优化。 他修饰的变量,该变量的修改 可能通过第三方来修改 多线程 硬件 char *p; volatile char *p ; *p == 0x10 while( *p == 0x10 ); xxxx;
关键字register寄存器
在声明局部变量的时候可以使用register关键字。 这就使得编译器把变量放入一个多用途的寄存器中,而不是在堆栈中, 合理使用这种方法可以提高执行速度。 函数调用越是频繁,越是可能提高代码的速度
访问固定内存位置--强制类型转换
在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。 int *p = (int *)0x67a9; p[0] = 0xaa66; *p = 0xaa66; *((int*)0x67a9) = 0xaa66; ((void (*)(void))0x67a9) ();
typedef类型
char *p; 什么类型 变量名称; int (*p[10])(int ,void (*p)(int)); char *name_t : name_t是一个指针,指向了一个char类型的内存 typedef char * name_t; name_t是一个指针类型的名称,指向了一个char类型的内存name_t是一个指针类型的名称typedef char * name_t; name_t是一个指针类型的名称,指向了一个char类型的内存 name_t abc;
2.指针初始化
指向数据类型
char *p;指向一个char类型数的指针 int *p; 指向一个int类型数的指针
指针名字*p
指针 int *p_zifuchuan int 代表这个盒子可以指向的 数据内容
取指针指向符号
指针变量
2.函数的定义
输入参数
调用者: 函数名(要传递的数据) //实参 被调者: 函数的具体实现 函数的返回值 函数名(接收的数据) //形参 { xx xxx } 实参 传递给 形参 传递的形式: 拷贝 承上启下的功能 输入参数的类型 值传递 地址传递(指针传递) 连续空间的传递(数组传递)
值传递
特点: void fun(int a) { a = xx; a = sha md5 yy } int main() { int a = 20; fun(a); printf a ==? } 上层调用者 保护自己空间值不被修改的能力
地址传递
上层,调用者 让下层 子函数 修改自己空间值的方式 类似结构体这样的空间,函数与函数之间调用关系---> 连续空间的传递 void fun(char a); int main() { char a = 'e'; fun(a); a == 'c' } void fun(char *b); int a = 10; fun(&a); a ===? 10 举例: int a; scanf("%d",a);//无法实现 scanf("%d",&a)//可以实现
作用
1、修改 int * short * long * 2、空间传递 2.1 子函数看看空间里的情况 const * 2.2 子函数反向修改上层空间里的内容 char * void * void fun(int *p); int buf[10]; fun(buf); int a = 10; fun(&a); a != 10 void fun(const int *p); void fun(const char *p); p[1000] p[100] p[10000] 空间:空间首地址、结束标志 结束标志:内存里面存放了0x00 (1B),字符空间 非字符空间 0x00,不能当成结束标志
连续空间的传递
1、数组 数组名 --- 标签 实参: int abc[10]; fun(abc) 形参: void fun(int *p) void fun(int p[10]) 2、结构体 结构体变量 struct abc{int a;int b;int c;}; struct abc buf; 实参: fun(buf); fun(&buf) 形参: void fun(struct abc a1) void fun(struct abc *a2)
字符空间
void fun(char *p) { p[1000] = '1'; char buf; buf = p[100]; } ------> void fun(char *p) const char *p { int i = 0; while(p[i]){ p[i]操作 p[i] =x; a=p[i] + - i++; } }
strlen
int strlen(const char *p) { int i = 0; /*错误处理,判读输入参数是否合法*/ if(p == NULL){ // return ... } /*内存处理,从头到尾逐一处理*/ while(p[i]){ //+++++++++++ i++; } }
strcpy
void strcpy(char *dest,const char *src); " " ---> 初始化const char * char buf[10] ---> 初始化char *
非字符空间
unsigned char *p; 结束标志:数量 (B) int *p unsigned char *p short *p struct abc *p void * : 数据空间的标识符 大小 void fun(unsigned char *p) { p[100] = xx p[1000] = yy; } void fun(unsigned char *p,int len) { int i; for(i = 0; i p[i] = ; a= p[i] //++++++++ -------- } } int main() { struct sensor_data buf; int buf1[100]; fun(&buf,sizeof(buf)*1) }
memcpy
void *
int fun(void *buf,int len) { unsigned char *tmp = (unsigned char *)buf; tmp[i] i++ len }
返回值
返回类型 函数名称(输入列表){ return } 调用者 a = fun(); 被调者:int fun(){return num;} 返回类型 基本数据 (将数据进行拷贝) 指针类型(空间) 数组(X)
返回基本数据类型
int fun(void) { return } struct abc fun(void); main: int fun1(void); int a=0; a = fun1();数据改变了 利用返回值 void fun2(int *p); int a = 0; fun2(&a); a数据改变了 利用指针 int [2] fun(void);//不能这样 只能返回一个值 int fun(int *); /*****************************************/ 这两个函数起到了相同的作用 int *fun1(void); int main() { int *p; p = fun1(); } void fun2(int **p) int main() { int *p; int p fun2(&p); p }
返回连续空间类型
指针作为空间返回的唯一数据类型 int *fun(); 地址:指向的合法性 作为函数的设计者,必须保证函数返回的地址所指向的空间是合法。【不是局部变量】 使用者: int *fun(); int *p = fun(); int p[100]; (X)
函数内部实现
基本数据类型 fun(void) { 基本数据类型 ret; xxxx ret = xxxx; return ret; } int fun() { int ret = 0; count++; ret = xxx; return ret; } char *fun() { char *s = NULL; char buf[]; return buf; }
1、静态区
static char *fun(void) { static char buf[]="hello world"; return buf; }
2、只读区
字符串常量 不太常用
3、堆区
malloc free
返回地址
指针保存函数
如何用指针保存函数那? char *p; char(*p)[10]; int (*p)(int,int,char);
int (*fun)() 指向函数首地址
int *fun()返回值为int 类型
2.数组空间初始化
数组空间的初始化 和 变量的初始化 本质不同, 尤其在嵌入式的裸机开发中,空间的初始化往往需要库函数的辅助
初始化语法
初始化修饰符
int a[10];
unsigned char buf[10];----> data
声明存储数据数组的空间
数据采集
非字符空间 数据采集 0x00 - 0xff 8bit
char buf[10]; ----> string
声明存储字符数组的空间
数组名
2.数组名: buf当成普通内存来看,没有问题 数组名是数组的首地址,一个常量符号,一定不要放到=的左边 举例:这是错误的例子 char buf[100];buf[100]; buf = "hello world";
数组运算符
空间
{10,20,30} //一堆的数字, a[10] //声明的一个空间
字符空间
字符空间 ASCII码编码来解码的空间,----》给人看 %s abc 'a ' 'b' ' c' \0作为结束标志
非字符空间
非字符空间初始化
方式1:数值数组初始化 unsigned char buf[10];----> data buf = "abc"; 方式2:数组指针初始化 unsigned char *p = sensor_base;
a[0] = 10逐次初始化变量
空间的赋值:按照标签逐一处理 int a[10]; [ 0 - 9 ] a[0] = xx a[1] = yy;
int a[10] = {10,20,30};一次初始化
这其实是程序员对复杂操作的一次封装; 空间的第一次赋值,初始化操作 int a[10] = 空间; C语言本身,CPU内部本身一般不支持空间和空间的拷贝 但是就是这句话行 int a[10] = {10,20,30}; ====> a[0] = 10; a[1] = 20; a[2] = 30; a[3]=0;
字符空间初始化
char buf[10]; ----> string //开辟一个存储这些数据盒子 char buf[5]={'h','e','l','l','o'}; 工作原理: 逐一处理 buf[0] = 'h' buf[1] = 'e' ... buf[n] = 'd', buf[n+1] = 0;
字符数组初始化char buf[5]={'h','e','l','l','o'};
1.字符串常量初始化char p2[] = "Another Hello";
再p2为首地址开辟一片空间 存下Another hello这个字符串 char buf[10] = {"abc"}; //可以带括号 char buf[10] = "abc"; //也可以不带括号 buf[2] = 'e';
char str[]; str="I am happy"
 错误的原因: C语言并没有提供可以直接操作字符串的运算符;“=”可以用于其他数据类型的赋值,但是不可以直接给字符串赋值。 char str[]; str="I am happy";//错误,字符数组的赋值只能按元素一一赋值(错误的原因: C语言并没有提供可以直接操作字符串的运算符;“=”可以用于其他数据类型的赋值,但是不可以直接给字符串赋值。
2.字符串常量初始化char *p1 = "Hello";
将字符串“hello”的地址赋给p1 char *message = "Hello World!"; // 上面的语句也可以拆分成下面两句 char *message; message = "Hello World!"; // 这句话看起来像是字符串复制,其实不是,只是涉及到指针操作
指针保存数组名
定义一个指针,指向int a[10]的首地址 定义一个指针,指向int b[5][6]的首地址 int *p1 = a; int **p2 = b; int *p[5]; int (*p)[5]; int b[2][3][4]; int (*p)[3][4];
数据声明和数据调用方式不同
1.数据申明 操作的对象 是被修饰的对象 例子:int *p=0x8849 被修饰的对象是 p 被操作的对象也是p 2.数据调用 *p=0xffff 被操作的是=号左侧的整体
num变量的使用
使用无符号整数u8,而不是整数的8
使用无符号整数,而不是整数的,如果你知道的值将永远是否定的。 有些处理器可以处理无符号的整数,比有符号整数的运算速度要快。 (这也是很好的实践,帮助self-documenting代码
p指针的使用
用指针代替数组
 这样做常常能产生又快又短的代码。 与数组索引相比, 1.指针一般能使代码速度更快, 2.占用空间更少。 3.使用多维数组时差异更明显 array的地址每次装入地址p后,在每次循环中只需对p增量操作。 在数组索引方法中,每次循环中都必须进行基于t值求数组下标的复杂运算
array数组的使用
数组的拷贝
拷贝三要素: 1、src 2、dest 3、个数 只管逐一拷贝,结束在哪里?只能定义个数
全局变量和函数传递参数的区别
使用全局变量比向函数传递参数更加有效率, 这样做去除了 1.函数调用前参数入栈和 2.函数完成后参数出栈的需要。 当然,使用全局变量会对程序有一些负作用。 1.然而决定使用全局变量会影响程序的模块化和重入,故要慎重使用。
函数的使用
【重点】 函数地址的表示方式 函数输入参数的表现形式及什么时候使用 函数返回值地址表现形式的应用场景及注意事项 【难点】 函数指针的意义 二级指针在函数输入参数的使用 函数具备3要素:3要素: 1、函数名 (地址)(地址) 2、输入参数 3、返回值 在定义函数时,必须将3要素告知编译器。 int fun(int,int,char) { xxx }
在不同的文件中调用函数
确保声明和定义是静态的,除非您希望从不同的文件中调用该函数
static静态函数
在同一文件函数对其他函数可见,才称之为静态函数。它限制其他访问内部函数,如果我们希望从外界隐藏该函数。 现在我们并不需要为内部函数创建头文件,其他看不到该函数。静态声明一个函数的优点包括: (1)两个或两个以上具有相同名称的静态函数,可用于在不同的文件。 (2)编译消耗减少,因为没有外部符号处理。
公司面试题目
1、统计一个int空间里1的个数 2、给你一个带有空格的字符串,删除其中空格
函数库
标准库
C 标准库 - 参考手册
C 标准库 - <assert.h>
C 标准库 - <ctype.h>
C 标准库 - <errno.h>
C 标准库 - <float.h>
C 标准库 - <limits.h>
C 标准库 - <locale.h>
C 标准库 - <math.h>
C 标准库 - <setjmp.h>
C 标准库 - <signal.h>
C 标准库 - <stdarg.h>
C 标准库 - <stddef.h>
C 标准库 - <stdio.h>
C 标准库 - <stdlib.h>
C 标准库 - <string.h>
strcpy,strncpy字符拷贝
一块空间,当成字符空间,提供了一套字符拷贝函数 字符拷贝函数的原则: 内存空间和内存空间的逐一赋值的功能的一个封装体 一旦空间中出现了0这个特殊值,函数就即将结束。 strcpy(); char buf[10] = "abc"; buf = "hello world"; strcpy(buf,"hello world");
memcpy
int buf[10]; int sensor_buf[100]; memcpy(buf,sensor_buf,10*sizeof(int)); unsigned char buf1[10]; unsigned char sensor_buf[100];// 00 00 00 23 45 78 strncpy(buf,sensor_buf,10) memcpy(buf,sensor_buf,10*sizeof(unsigned char));
C 标准库 - <time.h>
内存分配函数
void *malloc(unsigned int size)
其函数原型为void *malloc(unsigned int size);其作用是在内存的动态存储区中分配一个长度为size的连续空间。 返回值是分配区域的起始地址 如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。 函数返回的指针一定要适当对齐,使其可以用于任何数据对象。 // Code... char *Ptr = NULL; Ptr = (char *)malloc(100 * sizeof(char)); if (NULL == Ptr) {exit (1);} gets(Ptr); // code... free(Ptr); Ptr = NULL; // code...
内存分布图
内存的属性: 1、大小 2、在哪里 int a; 默认方式 编译 ---》汇编 ---》链接 *.o build 内核空间 应用程序不许访问 ------------------------------- 3G 栈空间 局部变量 RW ----------- 运行时的堆空间 malloc ----------- 全局的数据空间 (初始化的,未初始化) static RW data bss 只读数据段 "hello world" 字符串常量 R TEXT 代码段 code R TEXT ------------ 0x0 :
栈空间
运行时,函数内部使用的变量,函数一旦返回,就释放,生存周期是函数内
堆空间
运行时,可以自由,自我管理的分配和释放的空间,生存周期是由程序员来决定 分配: malloc(),一旦成功,返回分配好的地址给我们,只需要接受,对于这个新地址的读法,由程序员灵活把握,输入参数指定分配的大小,单位就是B。 char *p; p = (char *)malloc(100); if(p == NULL){ error } void fun() { char *p; p = (char *)malloc(100); return ; } int a[5]; malloc(5*sizeof(int)) 释放: free(p);
只读空间
静态空间,整个程序结束时释放内存,生存周期最长
嵌入式的编写技巧
中断
1.中断函数不能有参数 2.不能有返回值 3.要清除中断标志 4.不要在内部做复杂计算 5.不要在内部定义变量
避免使用标准库例程
因为很多大的库例程设法处理所有可能的情况,所以占用了庞大的内存空间, 因而应尽可能地减少使用标准库例程
手动编写汇编
在嵌入式软件开发中,一些软件模块最好用汇编语言来写, 这可以使程序更加有效。虽然C/C++编译器对代码进行了优化, 但是适当的使用内联汇编指令可以有效的提高整个系统运行的效率。