目录
1.c语言
(1)存储类型
存储类型:4
auto 声明自动变量,缺省时编译器一般默认为 auto
static 声明静态变量
register 声明寄存器变量---皇帝身边的小太监
存寻址访问以提高效率。注意是 尽可能,不是绝对
extern 外部变量声明
(2)数据类型
数据类型:7
void char short int
float double long
(3)修饰型
修饰型:4
signed 声明有符号类型变量
unsigned 声明无符号类型变量
const 声明只读变量
volatile 说明变量在程序执行中可被隐含地改变
(4)构造数据类型
构造数据类型: 4
struct 声明结构体变量
union 声明联合数据类型
enum 声明枚举类型
typedef 用以给数据类型取别名(当然还有其他作用)
(5)控制语句
各种优缺点综述:
if ... else
连续型特点:范围型
switch中的表达式可以是:
离散型特点:整型、字符型、或常量表达式或枚举。
while 和 do ...while 的差别在于第一次条件是否成立,如果条件不成立,则有区别。
break语句的作用是跳出当前循环。
如有嵌套的循环,则跳出最近的循环体。
continue:
continue语句的作用是跳出一次循环,----即忽略continue后面的其它语句,紧接着执行下一次循环。
**switch语句的基本形式:
switch (表达式)
{
case 常量表达式1:语句块1;break;
case 常量表达式2:语句块2; break;
….
case 常量表达式n:语句块n; break;
default :语句块n+1 break;
}
**case语句的排列顺序
按字母或数字顺序排列各条 case 语句。
如果所有的 case 语句没有明显的重要性差别,那就按 A-B-C 或 1-2-3 等顺序排列 case
语句。这样做的话,你可以很容易的找到某条 case 语句。
把正常情况放在前面,而把异常情况放在后面。
如果有多个正常情况和异常情况,把正常情况放在前面,并做好注释;把异常情况放在
后面,同样要做注释。
按执行频率排列 case 语句
把最常执行的情况放在前面,而把最不常执行的情况放在后面。最常执行的代码可能
也是调试的时候要单步执行的最多的代码。如果放在后面的话,找起来可能会比较困难,而放在前面的话,可以很快的找到。
一般来说 case语句后面的代码尽量不要超过 20 行。
**if..else
if语句:
if(condition){
语句1;
}
if ~ else语句:
if(condition){
语句1;
}else {
语句2;
}
if语句嵌套:
if(condition1){
语句1;
}else if(condition2){
语句1;
}else if(condition3){
语句3;
}else{
语句5;
}
**循环语句扩展
1.在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放
在最外层,以减少 CPU 跨切循环层的次数。
2.建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。
半开半闭区间写法和闭区间写法虽然功能是相同,但相比之下,半开半闭区间写法写法更加直观。
3.不能在 for 循环体内修改循环变量,防止循环失控。
4.循环要尽可能的短,要使代码清晰,一目了然。如果你写的一个循环的代码超过一显示屏,那会让读代码的人发狂的。解决的办法两个:
第一,重新设计这个循环,确认是否这些操作都必须放在这个循环里;
第二,将这些代码改写成一个子函数,循环中只调用这个子函数即可。一般来说循环内的代码不要超过 20行。
4.把循环嵌套控制在 3 层以内。
国外有研究数据表明,当循环嵌套超过 3 层,程序员对循环的理解能力会极大的降低。如果你的循环嵌套超过 3 层,建议你重新设计循环或是将循环内的代码改写成一个字函数
6.其他
(6)运算符与表达式
(优先级)单算移关与,异或逻条赋,逗号来结尾
(7)关键字
1.static
1.static:本质=====延长变量或函数的生命周期,同时限制其作用域
2.static声明的全局变量、函数,仅当前文件内可用,其他文件不能引用。
static在函数内部声明的内部静态变量,只需初始化一次。
而且变量存储在全局数据段(静态存储区)中,而不是栈中,其生命周期持续到程序退出。
3.
2.extern
1.谨记:声明可以多次,定义只能一次。
2.extern 函数或变量的外部声明
对变量、函数而言,如果要在当前文件内引用外部变量或外部函数,
就需要在使用前用extern声明该变量,或者在头文件中用extern声明该变量;
3.const
1.const: 用来定义一个常量: 关键是这个常量是什么,是指针还是变量
2.int const a; const int a; //a 是一个常整型数
const int * a; 意味着 a 是一个指向整型的指针(也就是,指针指向的整型数是不可修改的,但指针可以被修改)。
int * const a; 意思 a 是一个指向一个整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。
3.口诀:左数右指
当const出现在*号左边时,指针指向的数据不可以变,但指针可以。
当const出现在*号右边是,指针本身不可以改变,但指针指向的数据可以
/*************************************************************************
# FileName : const.c
# Author : fengjunhui
# Email : 18883765905@163.com
# Created : 2017年08月30日 星期三 15时25分56秒
************************************************************************/
#include<stdio.h>
int main(int argc, const char *argv[])
{
const int a = 10;
int b = 20,c = 0;
c = a + b;
//a ++; //const.c:17:2: error: increment of read-only variable ‘a’
printf("c: %d\n",c);
//const *
const int buf[5] = {1,2,3,4,5};
int *p = (int *)buf;
int i;
for(i = 0;i < 5;i ++)
{
p[i] = 11 - i;
}
//buf[3] = 10; //const.c:26:2: error: assignment of read-only location ‘buf[3]’
//printf("buf[3] :%d\n",buf[3]);
for(i = 0;i < 5;i ++){
printf("%d ",p[i]);
}
//* const array first addr cannot ++
int mybuf[] = {1,2,3,4,5};
int * const pp = mybuf;
for(i = 0;i < 5;i ++)
{
p[i] = 11 - i;
}
mybuf[3] = 10;
printf("mybuf[3] :%d\n",mybuf[3]);
for(i = 2;i < 5;i ++){
printf("%d ",pp[i]);
}
//pp ++; //const.c:47:2: error: increment of read-only variable ‘pp’
printf("pp[1]: %d\n",pp[1]);
printf("\n");
return 0;
}
测试结果:
fengjunhui@ubuntu:~$ ./a.out
c: 30
11 10 9 8 7 mybuf[3] :10
3 10 5 pp[1]: 2
4.volatile
1.volatile 易变的,不稳定的; 编译器警告提示字
防止编译器优化
如果使用了volatile修饰,那么保证每次取a的值都不是从缓存中取,而是从a所真正对应的内存地址中取.
原因:由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化
2.volatile用在如下的几个地方: 中断----多任务共享资源----mmu映射的寄存器
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
3.案例剖析(编译器的优化问题)
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
void *thread_func(void *arg)
{
sleep(2);
*(int *)arg = 0;
}
int main(int argc, const char *argv[])
{
pthread_t tid;
//volatile int a = 1;
int a = 1;
pthread_create(&tid,NULL,thread_func,&a);
while(a);
printf("----------------------\n");
return 0;
}
//编译器有优化的情况下:线程函数在睡眠2秒后修改a的值,输出printf
测试结果:
fengjunhui@ubuntu:~/Pthread$ gcc volatile.c -lpthread -O1
fengjunhui@ubuntu:~/Pthread$ ./a.out
^C
将a加volatile修饰,//volatile int a = 1;
编译器无优化的情况下:
fengjunhui@ubuntu:~/Pthread$ gcc volatile.c -lpthread -O3
fengjunhui@ubuntu:~/Pthread$ ./a.out
----------------------
(8)数组和指针
**指针
-
什么是指针?
在C语言中,内存单元的地址称为指针。
专门用来存放地址的变量,称为指针变量
在不影响理解的情况中,有时对地址、指针和指针变量不区分,通称指针
-
使用指针的好处?
-
使程序简洁、紧凑、高效
-
有效地表示复杂的数据结构
-
动态分配内存
-
得到多于一个的函数返回值
-
指针变量的赋值
-
指针的赋值运算指的是通过赋值运算符向指针变量送一个地址值
-
向一个指针变量赋值时,送的值必须是地址常量或指针变量,不能是普通的整数(除了赋零以外)
-
指针赋值运算常见的有以下几种形式:
//把一个普通变量的地址赋给一个具有相同数据类型的指针
double x=15, *px;
px=&x;
//把一个已有地址值的指针变量赋给具有相同数据类型的另一个指针变量.例如:
float a, *px, *py;
px = &a;
py = px;
//把一个数组的地址赋给具有相同数据类型的指针。例如:
int a[20], *pa;
pa = a; //等价 pa = &a[0]
4.若把一个变量的地址赋给指针,意味着指针所指向的内存单元实际上就是存储该变量的内存单元。因此,无论改变指针所指向的内存单元的内容还是直接改变变量的内容,都会有相同的效果。
**指针和一维数组
**指针和二维数组(数组指针)
-
引入:
C语言允许把一个二维数组分解成多个一维数组来处理。对于数组 a,它可以分解成三个一维数组,即 a[0]、a[1]、a[2]。每一个一维数组又包含了 4 个元素,例如 a[0] 包含 a【0】[0]、a【0】[1]、a【0】[2]、a【0】[3]。
假设数组 a 中第 0 个元素的地址为 1000,那么每个一维数组的首地址如下图所示:
指针 int *p;
数组 int a[4];
指针数组 int * a[4];
数组指针 int (*a) [4];
为了更好的理解[指针]和二维数组的关系,我们先来定义一个指向 a 的指针变量 p:int (*p)[4] = a;
括号中的*
表明 p 是一个指针,它指向一个数组,数组的类型为int [4]
,这正是 a 所包含的每个一维数组的类型。
[ ]
的优先级高于*
,( )
是必须要加的,如果赤裸裸地写作int *p[4]
,那么应该理解为int *(p[4])
,p 就成了一个指针数组,而不是二维数组指针。
对指针进行加法(减法)运算时,它前进(后退)的步长与 它指向的数据类型 有关,p 指向的数据类型是int [4]
,那么p+1
就前进 4×4 = 16 个字节,p-1
就后退 16 个字节,这正好是数组 a 所包含的每个一维数组的长度。也就是说,p+1
会使得指针指向二维数组的下一行,p-1
会使得指针指向数组的上一行。
数组名 a 在表达式中也会被转换为和 p 等价的指针!
下面我们就来探索一下如何使用指针 p 来访问二维数组中的每个元素。按照上面的定义:
-
p
指向数组 a 的开头,也即第 0 行;p+1
前进一行,指向第 1 行。 -
*(p+1)
表示取地址上的数据,也就是整个第 1 行数据。注意是一行数据,是多个数据,不是第 1 行中的第 0 个元素,下面的程序运行结果有力地证明了这一点:#include <stdio.h> int main(){ int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} }; int (*p)[4] = a; printf("%d\n", sizeof(*(p+1))); return 0; }
-
*(p+1)+1
表示第 1 行第 1 个元素的地址。如何理解呢? -
*(p+1)
单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。*(*(p+1)+1)
表示第 1 行第 1 个元素的值。很明显,增加一个 * 表示取地址上的数据。根据上面的结论,可以很容易推出以下的等价关系:a+i == p+i a[i] == p[i] == *(a+i) == *(p+i) a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)
实例:
1、编程实现,使用数组指针遍历二维数组
#include <stdio.h> int main(){ int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11}; int(*p)[4]; int i,j; p=a; for(i=0; i<3; i++){ for(j=0; j<4; j++) printf("%2d ",*(*(p+i)+j)); printf("\n"); } return 0; }
2、编程实现,使用行指针表示二维数组int a【3】[2]的元素a【1】[1]
#include <stdio.h> int main(int argc, char *argv[]) { int a[3][2] = {{1, 6}, {9, 12}, {61, 12}}; int (*p)[2], i, j; p = a; printf("%p %p\n", a, a+1); printf("%p %p\n", p, p+1); //使用行指针打印元素a【1】【1】 printf("%d, %d, %d, %d\n", a[1][1], p[1][1], *(*(a + 1)+1), *(*(p + 1) + 1)); //遍历数组 for (i = 0; i < 3; i++) { for (j = 0; j < 2; j++) printf("%d, %d, %d, %d ", a[i][j], p[i][j], *(*(a + i)+j), *(*(p + i) + j)); puts(""); } return 0; }
**指针数组
-
int *(p1[5]); //指针数组,可以去掉括号直接写作 int * p1[5];
-
int (*p2)[5]; //数组指针,不能去掉括号
指针数组和数组指针的区别指针数组和数组指针在定义时非常相似,只是括号的位置不同:
编程:利用指针数组处理一个二维数组,要求求出二维数组所有元素的和。
#include <stdio.h>
int main(int argc, char *argv[])
{
int a[2][3] = {{1, 4, 6}, {12, 9, 7}};
int * p[2] = {a[0], a[1]};
int i, j, sum = 0;
//指针p占多少个字节?
printf("total:%d\n", sizeof(p));
for (i = 0; i < 2; i++) {
for (j = 0; j < 3; j++)
sum += *(p[i]+j);
}
printf("sum=%d\n", sum);
return 0;
}
(9)字符串和字符数组
字符数组
有一定顺序关系的若干个字符型变量的集合,就是字符数组。可以是一维的,也可以是多维度的。
字符数组是 元素的数据类型 为字符类型的数组
字符数组具有普通数组的性质,又有一些特殊的性质。
-
字符数组的初始化
-
逐个字符赋值
-
char ch[5]={'H','e','l','l','o'};
char ch[6]={'H','e','l','l','o'};
char ch[6]={"Hello"};
char ch[6]="Hello";
char ch[]="Hello";
char fruit[][7]={"Apple","Orange","Grape","Pear","Peach"};
字符串
C语言中无字符串变量,用字符数组存放一个字符串,字符串结束标志:‘\0’
因此当一个字符串存入一个数组时,也把结束符'\0'存入数组,并以此作为该字符串是否结束的标志。
-
字符串的声明
char a[] = {'a', 'p', 'p', 'l', 'e', '\0'};
char b[] = {"apple"};
char c[] = "apple";
(10)函数
函数三要素:功能 参数 返回值
1.函数指针:
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int a = 10;
int b = 20;
//定义一个有2个指针的数组,指针指向一个函数,该函数有两个整型参数并返回一个整型数
int(*p[2])(int, int);
p[0] = Add; //函数指针数组的第一个元素指向Add函数
p[1] = Sub;
printf("%d, %d\n", p[0](a, b), p[1](a, b));
system("pause");
return 0;
}
2.指针函数
指针函数实质是一个函数。
指针函数实质是一个函数。函数都有返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。
1、指针函数定义格式:类型名 *函数名(函数参数列表);
int *pfun(int, int);
2、指针函数的声明、定义、调用
int * sum(int x); //指针函数的声明;返回类型位一个指针变量 可以通过*p来获取值
int * sum(int x){ //指针函数的定义
int static sum =0; //static 修身的变量在数据段;不会被函数栈回收
int *p;
int i;
for(i=1;i<=x;i++){
sum +=i;
return p; //返回类型是某一类型的指针
}
int *p1;
p1 = fun(a); //指针函数的调用
3、指针函数作用:可以在是代码更简洁并在一定程度节约内存;如当你需要返回一个数组中的元素时,你就只需返回首元素的地址给调用函数,调用函数即可操作该数组(让函数返回多个值)。
或者是malloc函数动态分配内存,返回该内存的地址给另外一个函数,另一个函数才好操作该内存。当然还有其他的作用,请读者自行在实践中体会。
4、注意:指针函数本质是一个函数,使用方法与普通函数没什么两样。既然是函数,就与变量不一样。变量需要定义、赋值、调用(比如函数指针);而函数需要声明、定义、调用。
3.回调函数
使用回调函数,和普通函数调用区别:
1)在主入口程序中,把回调函数像参数一样传入库函数。这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,且不需要修改库函数的实现,变的很灵活,这就是解耦。
2)主函数和回调函数是在同一层的,而库函数在另外一层。如果库函数对我们不可见,我们修改不了库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数了,这也就是在日常工作中常见的情况。
回调函数其实就是函数指针的一种用法:A "callback" is any function that is called by another function which takes the first function as a parameter。
注:使用回调函数会有间接调用,因此,会有一些额外的传参与访存开销,对于MCU代码中对时间要求较高的代码要慎用。
#include<stdio.h>
#include<freeLib.h>
// Callback Function
int Callback()
{
// TODO
func();
return 0;
}
// Main program
int main()
{
// TODO
Library(Callback);
return 0;
}
(11)结构体-联合体
1.常用写法
1.1
设计结构体类型的时候,同时定义结构体变量
struct{
int a;
int b;
}st;
这个st是一个结构体变量,无法在使用结构体来定义变量 (内核使用)
1.2
struct A{
int a;
int b;
}st;
这个st是一个结构体变量,可以通过struct A结构体类型来定义变量
1.3
设计结构体类型的时候,同时取别名
typedef struct A{
int a;
int b;
}st_t;
st_t 是一个结构体类型,它实际上是struct A的别名,此时除了struct A可以定义变量外,还可以通过st_t来定义(常用)
1.4
typedef struct{
int a;
int b;
}st_t;
st_t 是一个结构体类型,此时只能通过st_t类型来定义变量。(不常用)
2.结构体大小计算
2.1
5.结构体大小计算
gcc 编译器的对齐规则*******************4字节对齐
位置偏移:
char(1byte) short(2byte) int(4byte) float(4byte) double(4byte)
相对偏移(相对于结构体首地址的偏移量):
结构体成员分配内存的规则 -----> 相对偏移 % 位置偏移 == 0 就可以分配,如果不为0则补齐
最后要求:结构体的总大小 % 最大的位置偏移 == 0,如果不为0则补齐
struct内存对齐与取消内存对齐:
2.2 结构体计算代码
#include<stdio.h>
struct A{
char a; //1
short b; //2
int c; //4
};
struct B{
char a; //1
int b; //4
short c; //2
};
struct C{
int a; //4 0 % 4 = 0
char b; //1 4 % 1 =0
double c; //8 5 % 8 =5 +3 4+1+8+3 = 16
short d; //2 16 % 2 =0 =18
18 % 4 =2 +2 = 20内存对齐
}; //结构体的总大小 % 最大的位置偏移 == 0
struct D{
int a; //4
char b; //1
double c; //8
short d; //2
}__attribute__((__packed__)); //取消内存对齐 = 15
int main(int argc, const char *argv[])
{
printf("struct A size : %d\n",sizeof(struct A));
printf("struct B size : %d\n",sizeof(struct B));
printf("struct C size : %d\n",sizeof(struct C));
printf("struct D size : %d\n",sizeof(struct D));
return 0;
}
fengjunhui@ubuntu:~$ gcc sizeofstruct.c
fengjunhui@ubuntu:~$ ./a.out
struct A size : 8
struct B size : 12
struct C size : 20
struct D size : 15 //取消了内存的对齐
3.代码学习
3.1 结构体成员的访问
问:结构体变量在访问其成员的时候,如何访问?
答:本质上访问与数组一样,都是首地址 + 成员偏移量来访问。
在实际的编码中,使用原则如下:
<1>结构体的普通变量,在访问成员的时候,通过"."来访问
st.name st.age st.score
<2>结构体的指针变量,在访问成员的时候,通过"->"来访问
pst->name ,pst->age ,pst->score
注意:结构体的指针变量必须保存了一个有效的地址,它实际上访问的是它保存的地址中的成员
3.2 结构体成员排序
练习:向班级里添加5个学生信息,按成绩排序后再输出排序后的学生成员信息:
#include <stdio.h>
#define MAX 5
struct student{
char name[15];
int age;
int score;
};
struct class_room{
struct student stu[MAX];
int n;
};
void input(struct student *pstu)
{
printf("Name\tAge\tscore\n");
scanf("%s%d%d",pstu->name,&pstu->age,&pstu->score);
}
void input_student(struct class_room *pcr,int n)
{
int i;
for(i = 0;i < n;i ++){
input(&pcr->stu[i]);
pcr->n ++;
}
}
void output(struct student *pstu)
{
printf("%s\t%d\t%d\n",pstu->name,pstu->age,pstu->score);
return;
}
void output_student(struct class_room *pcr)
{
int i;
printf("\nName\tAge\tscore\n");
for(i = 0;i < pcr->n;i ++){
output(&pcr->stu[i]);//output(pcr->stu + i);
}
}
//扩展 : 排序算法:
//简单选择排序算法:
void student_simple_sort_score(struct class_room* pcr,int len)
{
int i,j,k;
struct student tmp;
for(i = 0;i < len-1;i++)
{
k=i;
for(j = i+1;j < len;j ++)
{
if(pcr->stu[j].score < pcr->stu[k].score)
{
k=j;
}
} //get the smallest value in first circle sort
tmp = pcr->stu[i];
pcr->stu[i] = pcr->stu[k];
pcr->stu[k] = tmp;
//place the smallest value in the first location
}
return;
}
//冒泡排序算法:
void sort_student(struct class_room *pcr)
{
int flag;
int i,j;
struct student tmp;
for(i = 0;i < pcr->n - 1;i ++){
for(j = 0,flag = 0;j < pcr->n - 1 - i;j ++){
if(pcr->stu[j].score > pcr->stu[j+1].score){
tmp = pcr->stu[j];
pcr->stu[j] = pcr->stu[j + 1];
pcr->stu[j+1] = tmp;
flag = 1;
}
}
if(!flag){
break;
}
}
return;
}
int compar_score(const void *p1,const void *p2)
{
struct student *stu1 = (struct student *)p1;
struct student *stu2 = (struct student *)p2;
return (stu1->score - stu2->score);
}
int main(int argc, const char *argv[])
{
struct class_room cr = {0};
input_student(&cr,MAX);
output_student(&cr);
printf("\n------------------------------\n");
//sort_student(&cr);
//快速排序
qsort(cr.stu,cr.n,sizeof(struct student),compar_score);
扩展:
// student_simple_sort_score(&cr,len);
// student_bubble_sort_score(&cr,len);
output_student(&cr);
return 0;
}
测试结果:
Name Age score
A 10 90
Name Age score
B 11 80
Name Age score
C 10 70
Name Age score
D 11 100
Name Age score
E 9 85
学生信息:
Name Age score
A 10 90
B 11 80
C 10 70
D 11 100
E 9 85
------------------------------
排序后的学生信息:
Name Age score
C 10 70
B 11 80
E 9 85
A 10 90
D 11 100
--------------------------------
对快速排序算法的单独理解:
NAME
qsort, qsort_r - sort an array 对数组成员排序
SYNOPSIS
#include <stdlib.h>
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
函数指针的调用:
#include <stdio.h>
#include <stdlib.h>
#define nmemb 7
int compar (const void *aa ,const void *bb)
{
int *a = (int * )aa; //类型强转,指定自己的数据类型
int *b = (int * )bb;
if( *a > *b) return 1; //做判断
if( *a == *b) return 0;
if( *a < *b) return -1;
}
int main( )
{
int base[nmemb]={ 3,102,5,-2,98,52,18};
int i;
for ( i=0; i<nmemb;i++)
printf("%d ",base[i]);
printf("\n");
qsort(base,nmemb,sizeof(int),compar);
for(i=0;i<nmemb;i++)
printf("%d ",base[i]);
printf("\n");
}
测试结果:
fengjunhui@ubuntu:~$ ./a.out
3 102 5 -2 98 52 18
-2 3 5 18 52 98 102
4.结构体嵌套访问
扩展:
struct体现其封装继承多态的思想
#include <stdio.h>
struct student{
char name[15];
int age;
int score;
};
struct small_student{
struct student stu;
void (*play)(char *game);
};
struct big_student{
struct student stu;
void (*coding)(char *lang);
};
void show(struct student *pstu)
{ }
int main(int argc, const char *argv[])
{
struct student stu;
struct small_student s_stu;
struct big_student b_stu;
show(&s_stu.stu);
show(&b_stu.stu);
show(&stu);
return 0;
}
5. 结构体和数组
问:结构体变量与单一数据类型变量区别在哪里?
答:结构体类型的变量是由多个单一数据类型的变量组合在一起的,在使用的时候, 可以对结构体内部的单一数据类型变量单独来访问。
问:数组也是多个成员组合在一起的,它与结构体变量的区别在哪里?
答:数组的成员全部是同类型,而结构体变量的成员可以是不同数据类型
6.扩展学习
扩展学习:
struct C{
int b;
char a;
double c;
short d;
}
无线通信的时候,数据包的(struct)的对齐规则不一样,要尤其注意
window ---平台
arm --android平台
Linux --Linux平台
跨平台进行数据传输的时候尽可能避免结构体,可以选择使用字符串代替结构体进行传输,例如sprintf()函数
6 共用体
6.1 什么是共用体
共用体 --- 成员共享内存,修改成员就是覆盖原成员
union 共用体名{
成员
};
问:它与结构体区别在于什么地方?
答:
<1>结构体使用struct关键字来定义,共用体使用union关键字来定义
<2>结构体的每个成员都有单独的内存空间,而共用体的所有成员共用同一块内存(最大的成员对应的内存)
<3>共用体变量与结构体变量访问成员的方法是一样
问:什么时候使用共用体?
答:如果需要使用的数据类型有很多,但是同一时刻只需要使用其中的一种数据类型
学生:小学生 中学生 大学生 研究生
6.2访问方法与测试
练习:验证union联合体(共用体)对成员的赋值是对原始数据的覆盖
#include<stdio.h>
union test{
unsigned int a;
unsigned short b;
unsigned char c;
};
int main(int argc, const char *argv[])
{
union test ut;
printf("ut :%p\n",&ut);
printf("ut.a :%p\n",&ut.a);
printf("ut.b :%p\n",&ut.b);
printf("ut.c :%p\n",&ut.c);
ut.a = 0x87654321;
printf("ut.a : %#x\n",ut.a);
ut.b = 0x8765;
printf("ut.b=0x8765 ut.a: %#x\n",ut.a);
ut.c = 0xff;
printf("ut.c=0xff ut.a : %#x\n",ut.a);
return 0;
}
fengjunhui@ubuntu:~$ gcc union.c
fengjunhui@ubuntu:~$ ./a.out
ut :0xbff6b8fc //占用的都是同一个地址
ut.a :0xbff6b8fc
ut.b :0xbff6b8fc
ut.c :0xbff6b8fc //占用的都是同一个地址
ut.a : 0x87654321
ut.b=0x8765 ut.a: 0x8765 8765 //小端存储,低字节发生了覆盖
ut.c=0xff ut.a: 0x876587 ff
6.3struct和union的优劣思考
扩展思考: struct与union的优势与劣势
struct{
占用了太多的空间: 是它的优势也是它的劣势
优势 ;允许对每个元素进行单独的内存空间操作
缺点 :内存开销比较大
}
(12) 宏定义-typedef -枚举
12.1各种宏定义学习
各种宏定义的学习和使用:
以mpu6050的inv_mpu.c文件为例:
只有一种可能的情况:
宏的定义:
#define MPU6050
#define AK89xx_BYPASS
宏变量的定义:
#define MAXLEN 1024
函数名替换:
#define i2c_write msp430_i2c_write
函数名加变量的替换
#define i2c_write(a, b, c, d) twi_write(a, b, d, c)
#define i2c_read(a, b, c, d) twi_read(a, b, d, c)
宏函数的简单定义:
#define fabs(x) (((x)>0)?(x):-(x))
#define min(a,b) ((a<b)?a:b)
宏函数的定义:
#define handle_error(msg) \
do {\
printk("%s----%d\n",__func__,__LINE__); \
perror(msg); \
exit(EXIT_FAILURE); \
}while (0)
只有两种可能的情况:
函数外两种情况下的宏开关:
#ifdef AK89xx_BYPASS
i2c_write(st.chip_cfg.compass_addr, AKM_REG_CNTL, 1, tmp+8);
#else
i2c_read(st.hw->addr, st.reg->raw_compass, 8, tmp);
#endif
函数内的宏开关
int mpu_get_compass_fsr(unsigned short *fsr)
{
#ifdef AK89xx_SECONDARY
fsr[0] = st.hw->compass_fsr;
return 0;
#else
return -1;
#endif
}
函数外的多重宏开关
#if defined MPU6050
functions......
#elif defined MPU6500
functions......
#elif defined MPU9250
.....
#endif
两重定义
#if defined AK8975_SECONDARY || defined AK8963_SECONDARY
#define AK89xx_SECONDARY
#else
#warning "No compass = less profit for Invensense. Lame." */
#endif
多重嵌套情况1
#if !defined MPU6050 && !defined MPU9250 && (defined AK8975_SECONDARY || defined AK8963_SECONDARY)
#error Which gyro are you using? Define MPUxxxx in your compiler options.
#endif
多重嵌套情况2
#if defined MPU9150
#ifndef MPU6050
#define MPU6050
#endif /* #ifndef MPU6050 */
#if defined AK8963_SECONDARY
#error "MPU9150 and AK8963_SECONDARY cannot both be defined."
#elif !defined AK8975_SECONDARY /* #if defined AK8963_SECONDARY */
#define AK8975_SECONDARY
#endif /* #if defined AK8963_SECONDARY */
#elif defined MPU9250 /* #if defined MPU9150 */
#ifndef MPU6500
#define MPU6500
#endif /* #ifndef MPU6500 */
#if defined AK8975_SECONDARY
#error "MPU9250 and AK8975_SECONDARY cannot both be defined."
#elif !defined AK8963_SECONDARY /* #if defined AK8975_SECONDARY */
#define AK8963_SECONDARY
#endif /* #if defined AK8975_SECONDARY */
#endif /* #if defined MPU9150 */
12.2简单应用
4====已知一个数组 table,用一个宏定义,求出数据的元素个数
#define NTBL (sizeof(table)/sizeof(table[0]))
2. 1、宏定义定义函数
#define handle_error(msg) do { perror(" msg"), exit(EXIT_FAILUER); }while(0)
2=====输入两个参数,输出较小的一个: #define MIN(A,B) ((A) < (B))? (A) : (B))
1=====答:交换两个参数值的宏定义为: . #define SWAP(a,b)\
(a)=(a)+(b);\
(b)=(a)-(b);\
(a)=(a)-(b);
6、得到一个字的高位和低位字节
#define WORD_L(***) ((byte) ((word)(***) & 255))
#define WORD_H(***) ((byte) ((word)(***) >> 8))
注意:
gcc -E test9.c -o test9.i -DSAMSUNG ( -D编译代码的时候,同时指定宏定义)
12.2宏定义和typedef的区别
答: Typedef 在 C 语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。
例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s* tPS;
以上两种情况的意图都是要定义 dPS 和 tPS 作为一个指向结构 s 指针。哪种方法更好呢?(如果有的话)
为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是: typedef 更好。
思考下面的例子:
dPS p1, p2;
tPS p3, p4;
第一个扩展为
struct s * p1, p2;
上面的代码定义 p1 为一个指向结构的指针, p2 为一个实际的结构体,这也许不是你想要的。 第二个例子正 确地定义了 p3 和 p4 两个指针。
1) #define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。
2)typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名,但是You cannot use the typedef specifier inside a function definition。
12.3枚举
枚举
特点:有限罗列
enum 名字{
罗列的成员1,
罗列的成员2,
};
问:宏定义与枚举的区别
答:枚举是一中类型,可以用来定义变量,可以包含多个枚举成员,而宏是替换,没有类型,也没有多个成员
强调:enum;
1、枚举成员的每一个成员都是整数
2、分隔符:为逗号‘,’
3、可以直接赋值:struct和union都不行。
4、枚举的成员直接访问
13内存管理
1.基础malloc分配
2.动态分配升级
typedef struct node{
int* pnode;
int a;
char b;
short c;
struct node *next;
}node_t;
node_t *create_mynode(int num)
{
node_t *mynode = (node_t *)malloc(sizeof(node_t));
mynode->pnode = malloc(sizeof(int) * num);
if(mynode->pnode == NULL)
return NULL;
return mynode;
}
堆、栈
C内联汇编
野指针
内存泄漏
14.shell脚本编程
14.1 基本命令练习:
mkdir $HOME/subdir1 $HOME/subdir2
cp /etc/passwd subdir1 -a
cp /etc/group subdir2 -a
mv subdir2 subdir
tar -cvf subdir1.tar.xz subdir1
cp subdir.tar.xz subdir
cd subdir
tar -xvf subdir.tar.xz
ls -l
1.在自己的用户主目录下新建两个子目录subdir1 subdir2
2.将/etc/passwd文件拷贝到subdir1 ,将/etc/group 拷贝到subdir2
3.将subdir2重命名为subdir
4.对subdir1进行打包并且压缩成xz格式
5.将打包后的xz格式文件拷贝到subdir目录下
6.解压subidr目录下的压缩格式文件
7.查看subidr目录下所有的文件
14.2shell脚本语言语法
1.变量
2.输入输出
3.数据运算
....
15 GCC交叉编译
1.预处理
2.编译
3.汇编
4.链接
2.数据结构
(1)顺序表
参考(79条消息) 数据结构(1)_sqlist在哪个头文件_日落%c的博客-CSDN博客
(2)链表
2.1单向链表(增删改查)
(1)头结点定义
typedef int datatype_t;
typedef struct node{
datatype_t data;
struct node *next;
}LinkNode;
(2)创建
LinkNode* create_empty_linklist(void)
{
LinkNode *head;
// void *malloc(size_t size); //申请头结点空间
head = (LinkNode *)malloc(sizeof(LinkNode));
if(head == NULL)
{
printf(" No free memery! \n");
return NULL;
}
head->next = NULL; //填充头结点
return head;
}
(3)插入
1.头插法
LinkNode* head_insert_linklist(LinkNode* head,int DATA)
{
LinkNode* tmp = (LinkNode*)malloc(sizeof(LinkNode));
tmp->next = head->next;
head->next = tmp;
tmp->data = DATA;
return head;
}
2.尾插法
LinkNode* tail_insert_linklist(LinkNode* head,int DATA)
{
LinkNode *p;
for(p = head; p->next != NULL; )
{ p = p->next; }
LinkNode *tmp = (LinkNode*)malloc(sizeof(LinkNode));
tmp->next = NULL;
p->next = tmp;
tmp->data = DATA;
return head;
}
(4)删除
LinkNode* delete_data_from_linklist(LinkNode* head,int DATA)
{
int flag =0;
LinkNode* p; //链表内元素的删除,一个指针用于查找,一个指针用于定位,一前一后
LinkNode* q;
for(p = head->next,q = head; p != NULL; )
{
if(p->data == DATA )
{
q->next = p->next;
free(p);
p = q->next;
flag =1;
}
else
{
p = p->next;
q = q->next;
}
}
if(!flag)
{
printf("No such DATA in linklist !\n");
}
return head;
}
(5)释放链表
void destroy_linklist(LinkNode* head)
{
LinkNode* p;
if(head == NULL) return;
while(head->next != NULL) //释放每一个节点(删除每一个节点)
{
p = head->next;
head->next = p->next;
free(p);
}
free(head);
return;
}
(6)有序插入
LinkNode* insert_order_linklist(LinkNode* head,int DATA)
{
LinkNode* p = head->next;
LinkNode* q = head;
//链表有序
while( p !=NULL) //查找插入的节点位置,如果查找到位置,则break
{
if(p->data < DATA)
{
p = p->next;
q = q->next;
}else{
break;
}
}
// head_insert_linklist(q,DATA);
#if 1
LinkNode* tmp = (LinkNode*)malloc(sizeof(LinkNode));
if(tmp ==NULL)
{
printf("No free memery !\n");
return NULL;
}
tmp->data = DATA; //插入
tmp->next = p;
q->next = tmp;
#endif
return head;
}
(7)逆置
LinkNode* reverse_linklist(LinkNode* head)
{
LinkNode* p;
LinkNode* t;
p = head->next;
head->next = NULL;
while(p != NULL)
{
t = p->next;
p->next = head->next;
head->next = p;
p = t;
}
return head;
}
2.循环链表
1-1 单向循环链表:
在linklist,单链表的基础上稍微做些修改,将单链表的尾部节点的next指针指向头
节点,就构成了单向循环链表。
3.应用实例
(1)遍历一次链表,找到链表的中间节点
关键算法分析: 定义两个指针,一个走一步,一个走两步,走两步的next节点为
NULL的时候,走一步的指针刚好指向中间节点;
struct node *q = head->next;
struct node *p = head;
for( ;q != NULL;p=p->next,q=q->next)
{
q = q->next;
}
printf("%d ",p->data);
(2).已知道p指向的节点,如何删除p指向的节点,p不是头节点,p也不是尾部节点 、
删除当前节点的后一个节点:
算法思路:
for( ; p != NULL; p=p->next)
{
tmp = p->next; //记录下一个节点
printf("%d ",p->data); //输出删除的节点数据20
p->data = tmp->data; //将tmp的数据拷贝到节点p当中
p->next = tmp->next; //p->next记录tmp的next节点信息
free(tmp);
}
(3)已知链表1和链表2各自有序,
将其合并成一个链表之后依然有序,请用递归的方法实现。
typedef struct node{
datatype_t data;
struct node *next;
}Linknode;
Linknode* creat_empty_linklist()
{
Linknode* head = (Linknode* )malloc(sizeof(Linknode));
head->next = NULL;
return head;
}
void insert_linklist(Linknode* head,datatype_t data)
{
Linknode* tmp = (Linknode*)malloc(sizeof(Linknode));
tmp->data = data;
tmp->next = head->next;
head->next = tmp; //头插法
return;
}
Linknode* merge_recursive(Linknode* head1,Linknode* head2)
{
Linknode *head = NULL;
if(head1 == NULL) return head2;
if(head2 == NULL) return head1;
if(head1->data > head2->data) //数据大则head为head1的头指针数据,递归
{
head = head1;
head->next = merge_recursive(head1->next,head2);
}else
{
head = head2;
head->next = merge_recursive(head1,head2->next);
}
return head;
}
3.数组与链表的对比
数组
问:许多数据,如何存储?
答:
<1>数组存储 (顺序表)
优点:
数据连续存放,我们只需要数据存放的首地址,可以通过下标直接访问,不需要遍历
缺点:
因为数组的大小是固定,不便于扩展
链表
优点:
可以根据数据的个数,来动态分配内存,便于扩展 malloc --- free
缺点:
由于是动态分配内存,内存是随机分配,无法直接访问,需要遍历
(3) 栈(后入先出)
3.1 栈的结构体
栈中数据节点数据结构:
typedef struct node{
datatype data;
struct node *next;
}LinkNode;
栈头的数据结构
typedef struct{
LinkNode *top; //记录栈顶的位置(新进栈的数据)
int n; //记录栈中数据的个数
}LinkStack;
3.2栈的基础操作
1.创建栈
LinkStack* create_empty_linkstack()
{
LinkStack *s ;
s = (LinkStack*)malloc(sizeof(LinkStack));
s->top = NULL;
s->len = 0;
return s;
}
2.压栈
int push_element_to_linkstack(LinkStack*s , datatype_t DATA)
{
LinkNode* tmp;
tmp = (LinkNode*)malloc(sizeof(LinkNode));
tmp->data = DATA;
tmp->next = s->top;
s->top = tmp;
s->len ++;
return s->len;
}
3.出栈
datatype_t pop_element_from_linkstack(LinkStack* s)
{
datatype_t data;
LinkNode *tmp;
if(is_empty_linkstack(s))
{
printf("The LinkStack is empty!\n");
return NULL;
}
tmp = s->top;
s->top = tmp->next;
data = tmp->data;
free(tmp);
s->len --;
return data;
}
4.判断栈顶元素
datatype_t get_linkstack_top_element(LinkStack *s)
{
if(is_empty_linkstack(s))
{
printf("The LinkStack is empty!\n");
return NULL;
}
return s->top->data;
}
5.栈空
int is_empty_linkstack(LinkStack* s)
{
return s->top == NULL? 1 : 0;
//return s->len <= 0 ? 1 : 0;
}
6.可以设置栈的最大元素空间,int MAX如果不设置,栈可以无限增大
(4)队列
原则:先进先出
操作:
尾部进队,头部出队
1.顺序队列
2.循环队列
3.链式队列
四 链式队列
队列中数据节点数据结构
typedef struct node
{
datatype data;
struct node *next;
}LinkNode;
队列头节点数据结构
typedef struct{
LinkNode *front;
LinkNode *rear;
}LinkQueue;
创建
LinkQueue *create_empty_linkqueue()
{
LinkNode *head = (LinkNode*)malloc(sizeof(LinkNode));
LinkQueue *q = (LinkQueue*)malloc(sizeof(LinkQueue));
head->next = NULL;
q->front = head;
q->rear = head;
return q;
}
空链式队列
int is_empty_linkqueue(LinkQueue *q)
{
if(q->front == q->rear)
{
printf("is_empty_linkqueue,NOW!\n");
}
return q->front == q->rear ? 1 : 0;
}
入链式队列
void enter_linkqueue(LinkQueue *q,datatype_t data)
{
LinkNode* tmp;
tmp = (LinkNode *)malloc(sizeof(LinkNode));
if(tmp == NULL){
printf("Fail to malloc:no free memory!\n");
return;
}
tmp -> data = data;
tmp->next = NULL;
q->rear->next = tmp;
q->rear = tmp;
return;
}
出链式队列
int delete_linkqueue(LinkQueue *q)
{
LinkNode *tmp;
datatype_t data;
tmp = q->front;
q->front = tmp ->next;
data = tmp->next->data;
free(tmp);
#if 0 //过于冗余,不好理解
data = q->front->next->data;
tmp = q->front->next;
q ->front = tmp->next;
free(tmp);
#endif
#if 0
if(tmp == NULL)
{
q->rear = q->front;
}
#endif
return data;
}
(5)串串
(6)树
(7)哈希表与内核链表
(8)查找
(9)排序
1.插入排序
Insert the sort directly
插入排序:(插入之前的原始数据必须有序)
算法思路:
//插入排序算法特点: 尾插法,要插入的空间开始为空,就像拿着五个大小不同的球放到五个篮子里一样.
当要插入的数据a[j+1]比前一个数据大a[j]的时候,就插入到前一个数据a[j]的后面a[j+1]
当要插入的数据a[j+1]比前一个数据a[j]小的时候就把上一个数据a[j]保存到要插入的位置a[j+1];
如果要插入的数据a[j+1]仍然比当前数据a[j]要小时,j--,重复上述过程
通过构建有序序列,对于未排序的数据,在已排序的序列中从后向前进行扫描,
找到相应的位置并插入,插入排序在实现上,需要反复把已排序元素逐步向后移动
为新插入的元素提供插入空间:
2.交换排序(冒泡排序)
Bubble Sort
冒泡排序: 冒泡冒泡,将最小的泡泡冒到最上面
基本思想: 对待排序记录的数据从后往前进行多遍扫描,当发现了个相邻的
数据与排序条件不符是,将两个数据进行交换,这样较小的数据逐渐从后
往前面移动,就像气泡在水中上浮一样,所以称之为冒泡排序。
对冒泡排序法的改进:
当在某一遍扫描时,发现数据都已经按顺序排列了,就不在进行后续扫描,本次扫描结束
推导: 10个数据要扫描9次,改进后的算法不需要对已经有序的数据从新排序了
2.交换排序(快速排序)
算法思路:
key = a[low]
a[] = {4,2,8,6,11,1,0,5,3,9};
i j
以key为基准值,将比基准值小的值放到左边,比基准值大的值放到右边
所以算法思路:
右边先开始走,当出现比key小的值时,j停止;
记录j的值,将比key小的值放到左边,a[i] = a[j]; //a[i]的值由key保存
左边开始走,当出现比key大的值时,i停止;
记录i的值,将比key小的a[i]的值放到右边,a[j] = a[i];
a[i] = key;
重复上述过程
int quick_sort(int* a,int low,int len)
{
static int count = 0;
int i = low;
int j = len;
int key = a[low];
while(i < j)
{ //从右往左查找第一个比基准小的数据,然后和base交换
while((i < j) && (a[j] >= key)) j--;
if(i < j) a[i] = a[j];
//从左往右查找第一个比基准大的数据,然后和base交换
while((i < j) && (a[i] <= key)) i++;
if(i < j) a[j] = a[i];
}
a[i] = key;
count++;
if(i+1 < len)
{
quick_sort(a,i+1,len);
}
if(low < j-1)
{
quick_sort(a,low,j-1);
}
return count;
}
3.选择排序
Simple sorting
简单选择排序:
基本思想:
对n个记录进行扫描,选择最小的记录,将其输出至第一个位置,
接着在剩下的n-1个记录中继续扫描,选择最小的记录将其输出....不断重复,
直到只剩下一个记录为止。
这个和斗地主一样: 将拿到的扑克牌进行选择排序,以第一张扑克牌为基础,遍历手中的扑克牌,
如果比第一张小,就和第一张交换位置,...直到所有的扑克牌按照从大到小的顺序排列起来。
举例: 2 1 3 5 4
min =2 1<2 1<3 1<5 1<4 ====> a[0] =1 1 2 5 3 4
2<3 2<5 2<4 ====> a[1] =2 1 2 5 3 4
5>3 ---> 3 5 --->3<4 ---> ====> a[2] =3 1 2 3 5 4
5>4 ---->交换 ======> a[3] = 4; a[4] =5; 1 2 3 4 5
3.IO进程
(1)文件IO
posix(可移植操作系统接口)定义的一组函数,不提供缓冲机制,每次读写都引起系统调用
核心概念是文件描述符 访问各种类型文件(Linux下标准io基于文件io实现)
(2)库函数和系统调用
(3)标准IO
1.对文件流的操作FILE *fp; fopen fread fwrite fclose fseek
fopen:
FILE *fopen(const char *path, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);
freopen函数将stream流指针重新绑定到字符串path所指向的文件中,
The original stream (if it exists) is closed.
The primary use of the freopen() function is to change the file associated with
a standard text stream (stderr, stdin, or stdout).
mode: r r+ w w+ a a+
r: 读已存在的文件,并定位到文件头 r+,读写已存在的...
w: 写truncate清空文件,不存在则创建文件,定位到头, w+,读写...
a: 在文件末尾追加内容,如果不存在则创建,定位到尾部,a+,如果是读,则从头读,如果是写,则尾部开始写,只针对第一次操作
第二次开始之后根据文件流指针来操作。
(4)库的制作
(5)进程
进程: 进程是一个独立的可调度的任务
进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源
进程是一个程序的一次执行的过程
进程的种类:
交互进程
批处理进程
守护进程
(6)EXEC函数族
(7)线程
(8)进程间通信
无名管道/有名管道
信号:
(9)ipc对象操作(日后总结) (略)
(10)线程间通信
man命令(***)
man手册:
man 1,用户命令手册
man 2,Linux内核系统调用手册
man 3,标准库函数手册
man 1 pwd ---------用户命令手册
NAME
pwd - print name of current/working directory
pwd [OPTION]...
man 2 exit ----Linux内核系统调用手册
_EXIT(2) Linux Programmer's Manual
NAME
_exit, _Exit - terminate the calling process //退出系统调用 open read write
man 3 exit 标准库函数手册
EXIT(3) Linux Programmer's Manual fopen fread fwrite
NAME
exit - cause normal process termination
4.网络
(1)OSI模型和TCP/IP模型
(2)网络各层协议解释
(3)基础概念
1.套接字
2.IP地址
3.端口号
4.字节序
5.子网掩码
6.网关和广播
(4)服务器模型搭建及并发
&&(日后补充,或者另外写一份文章)
tcp
udp
(79条消息) 深入理解select、poll和epoll及区别_poll epoll_panamera12的博客-CSDN博客
(5)网络超时检测
(6)其他网络通信
5.ARM体系结构和接口技术
(1)计算机硬件基础
组成
总线:
DMA总线:
DMA(Direct Memory Access)即直接存储器访问,使用DMA总线可以不通过CPU直接在存储器之间进行数据传递
普通总线:
总线是计算机中各个部件之间传送信息的公共通信干线, 在物理上就是一束导线按照其传递信息的类型可以分为数据总线、地址总线、控制总线
(2)多级存储结构与地址空间
(3)CPU工作原理概述:
(4)ARM处理器概论
1.起源
2. ARM指令集概述
1.什么是指令集
(什么是指令
能够指示处理器执行某种运算的命令称为指令(如加、减、乘 ...)
指令在内存中以机器码(二进制)的方式存在
每一条指令都对应一条汇编
程序是指令的有序集合
)
处理器能识别的指令的集合称为指令集
不同架构的处理器指令集不同
指令集是处理器对开发者提供的接口
2.soc:即片上系统,将一个系统中所需要的全部部件集成在一个芯片中在体积、功耗、价格上有很大优势
内存什么和cpu集成在一起
CISC处理器
不仅包含了常用指令,还包含了很多不常用的特殊指令,硬件结构复杂,指令条数较多,一般指令长度和周期都不固定
CISC处理器在性能上有很大优势,多用于PC及服务器等领域
复杂指令集
RISC处理器
只保留常用的的简单指令,硬件结构简单,复杂操作一般通过简单指令的组合实现,一般指令长度固定,且多为单周期指令
RISC处理器在功耗、体积、价格等方面有很大优势,所以在嵌入式移动终端领域应用极为广泛
保留常用的指令 经典指令集 指令所占长度一样
3.编译原理
4.ARM存储模型
5.ARM工作模式
(5)ARM异常处理
arm产生异常后的动作(自己完成)
ARM异常返回的动作(自己编写)
1.将SPSR_<mode>的值复制给CPSR
使处理器恢复之前的状态
2.将LR_<mode>的值复制给PC
使程序跳转回被打断的地址继续执行
IRQ异常举例
异常优先级![](https://i-blog.csdnimg.cn/blog_migrate/d1363d4fe1bbc31593292dfc947713f7.png)
(6)ARM微架构
(7)ARM指令集仿真环境搭建
**后续有关汇编以及直接操控寄存器控制外设等。。有时间再写
6.uboot与kernel启动与移植流程分析
(1)开发板启动过程
(2)交叉开发环境搭建
1.Ubuntu网络配置:
2.TFTP服务器环境搭建
2.nfs服务器搭建
(3)uboot的烧写和使用
1.什么是BootLoader![](https://i-blog.csdnimg.cn/blog_migrate/93e8c38521af46f69c4960b85531186b.png)
2.sd卡启动盘制作
步骤
3.uboot的使用:
uboot的环境变量命令
(4)Linux内核的安装与加载
1.tftp加载Linux内核及rootfs
2.EMMC加载Linux内核及rootfs
3.tftp加载Linux内核nfs挂载rootfs
4.EMMC加载uboot
(4)Linux内核(+设备树)移植
1.linux内核概述
2.Linux内核的配置与编译(一)
指定处理器架构及编译工具
在Linux内核源码顶层目录下的Makefile中指定(ARCH、CROSS_COMPILE)
导入当前处理器的默认配置
make <soc_name>_defconfig
注1:soc_name为当前使用的处理器的名字
注2:内核源码的arch/arm/configs下对各个厂商的soc都有一个默认配置文件
执行该命令后就会将对应的配置文件中的信息导入到源码顶层目录下的.config
文件中CONFIG_xxx=y表示内核选中了该功能,内核编译时就会将该功能对应的
代码编译,内核的体积也会增大。#CONFIG_xxx is not set表示内核没有选中
该功能,内核编译时该功能对应的代码不会被编译,内核的体积也会减小。
linux内核源码编译
3.linux设备驱动移植(设备树)
(5)根文件系统移植
7.驱动初级
(1)字符设备驱动框架
(2)IO模型
(3)中断与时钟
(4)内存分配
(5)设备模型
(6)platform总线