C语言学习
C语言学习
视频教程
https://www.bilibili.com/video/BV1p7411p78H?p=36
运算符
自定义数据结构
struct
union
起始地址相同的一段内存空间
union myabc{
int a;
unsigned int b;
}
enum
typedef
对原有内存空间的别名,就是给上面定义的数据类型换个名字
typedef使用意义
//阅读性差
int a=179;
int b=9600;
//阅读性强
len_t a=179;
speed_t b=9600;
typedef跟指针交叉使用,定义也会更加方便(会少很多*)
使用
int a;//a是一个int类型的变量
typedef int a_t;//a是一个int类型的外号
/*
*使用规范:一般使用别名 定义为 XX_t
*/
a_t mysize;
测试用例
#include <stdio.h>
int main(){
int a;//a是一个int类型的变量
typedef int a_t;//a是一个int类型的外号
/*
*使用规范:一般使用别名 定义为 XX_t
*/
//使用
a_t mysize;
mysize=1;
printf("%d\n",mysize);
}
逻辑结构
if\else
switch\case\default
switch(整形)
do\while\for
continue\break\goto
goto在函数内部跳转十分好用,不能在不同函数之间跳转
类型修饰符
资源属性中位置的限定,由类型修饰符限定
使用场景举例子
比如在我们嵌入式开发中,手机的开机画面我们一般是不允许别人修改的,因此我们会对内存存放位置以及资源存放位置进行一些限定,这些都跟内存存放地址相关
auto
默认,分配的内存都是可存可写的
区域如果在{}当中,则是在栈空间当中
register
限制变量放在寄存器当中
内存(CPU外部的存储器)
寄存器(CPU内部的存储器)
此关键词中看不中用,编译器会尽量的安排CPU的寄存器去存放此变量,若寄存器不足时,则此变量还是放在内存中。
&符号对register定义的变量是不起作用的
static
静态
应用场景
修饰三种数据:
- 函数内部的变量
int fun(){
int a; ===> static int a;
}
- 函数内部的变量
int a; ===> static int a;
int fun(){
}
- 函数
int fun(); ===>static int fun()
const
常量的定义,C语言的软肋,中看不中用
只读的变量,其实值还是可以改变的
extern
外部声明
volatile
他实际的操作对象是编译器
告知编译器变异方法的关键字,告诉编译器不优化编译。
举个例子
int a=100; //此变量是通过外部条件触发修改,例如按下键盘修改ad的值
while(a==100);
{
//当外部事件没有触发则在这里死循环
}
mylcd();
----------------------------
[a]:a的地址
f1: LDR R0, [a]
f2: CMP R0, #100
f3: JMPeq f1 ------> JMPEQ f2 优化会直接从f2开始而不是每次去外部内存获取[a]的值
f4: mylcd()
而我们是需要每次去获取外部内存的值得,因此a需定义为volatile类型
修改变量的值得修改,不仅仅可以通过软件,也可以通过其他的方式(硬件外部的用户)
运算符
算数运算符
+ -
* \ %
乘除在大部分的cpu中是不支持的
int a=b*10; //CPU可能多个周期,甚至要利用软件的模拟方法去实现乘法
int a=b+10; //CPU一个周期可以处理
%的使用
取一个0-100内的随机数
(m%100)+1
逻辑运算符
返回结果: 1 or 0
|| &&
A||B 跟 B||A是不同的
A||B中,若A已经为真则B不会再执行了
测试
void test_yihuo(){
int a=10;
int ref=a;
if ((ref==a)||printf("测试"))
{
}
}
发现没有打印测试
> >= < <=
!
对比位运算的取反
int a=0x0;
!a 1
~a 0xffff
? :
位运算
嵌入式中十分重要的运算方式
<< >>
m<<1; m*2
m<<n; m*2^n
左移:乘法*2 二进制下的移位
举例
4: 00100
8: 01000
[数据、数字]
举例
-1 *2 : -2
以8bit为例
10000001
取反:11111110
加一:11111111
此时是-1在内存中的存储状态
-2
10000010
取反:11111101
加一:11111110
此时是-2在内存中的存储状态
右移:
跟符号变量有关
除法\2
例子
int a;
a>>n;
unsigned int a;
a>>n;
int a=-10;
二进制:10001010;
a>>8; 00000001
a>>
& | ^
&的使用
A&0------>0 其中A是Bit
&的屏蔽作用
int a=0x1234;
a & 0xFF00;屏蔽低bit,取出8bit
&(清理器)
|的使用
A|0------>A
保留作用
A|-------->1
设置高电平的方法,设置set
实例
设置bit5为高电平,其他位不变
1 0 0 0 0 0
bit5 bit4 bit3 bit2 bit1 bit0
int a;
a=(a|(0x1<<5));
a|(0x1<<n);//设置第n位为高电平
清除bit5
int a;
a=a&-(0x1<<5);
a&-(0x1<<n);//清除第n位
分析:16位操作系统中
00000000 00100000
11111111 11011111//取反
做&操作即可取出
^(异或)的使用
^(异或)操作和~(取反)操作都是逐位操作,每一个bit都会进行操作
1^1=0
0^0=0
1^0=1
一般都是在算法中使用,比如: AES SHA1
两数交换
int a =10;
int b=20;
a=a^b;
b=a^b;
a=a^b;
~(取反)操作的使用
实例
0xF0 1111 0000
取反得
0x0F 0000 1111
赋值运算
=
+=、-=、&=、|=
内存访问符号
( ) [ ] { }
[ ]内存访问的ID符号
-> .
对于连续空间和自定义空间中不同成员变量的访问方法
其中->为地址访问
.为变量访问
& *
&变量名 为取地址
*变量名 为指针
C语言内存空间的使用
指针
内存资源的”门牌号“,资源地址。
指针变量
存放指针这个概念的盒子
C语言编译器对指针这一特殊的概念有两个疑问
1、分配一个“盒子“,盒子要多大?
2、“盒子”里存放的地址,所指向的内存读取方式是什么?
回答
在32位系统中,指针为4个字节
实例
void test_zhizhen2(){
int a=0x12345678;
char *p1; //char类型只占一个字节
p1=&a;
char *p2;
printf("p1_val: %x \n",*p1);//此时取出一个字节 78
}
指针指向内存空间,一定要保证合法性,若非法则会出现段错误
所以当出现段错误时,优先去看代码中指针部分
变量的分配是从高往低分配的
越界实例
void TestMemoryYuejie(){
const int a=0x12345678;
int b=0x87654321;
int *p=&b;
p[1]=0x123;
printf("%x",a);//=> 123
}
逻辑操作符
>= <= == !=
常用的操作主要是 ==和!=;比较指针地址的大小关系无意义
- 跟一个特殊值进行比较;比如:
0x00:地址无效值、结束标识
if(p==0x00)
- 指针必须是同类型的比较才有意义,才能编译过去
char *与int *比较不了
多级指针
存放地址内容的盒子
int **p;
*p; //p指针
int *; //存储类型为指针的方式
char **p;如下图
多级指针的实例
int main(int argc,char **argv){
for (int i = 0; i < argc; i++)
{
printf("argv[%d]:%s\n",i,argv[i]);
}
int j=0;
while(argv[j]!=NULL){
printf("argv:%s\n",argv[j]);
j++;
}
}
其中char **argv便是多级指针的实例
数组
内存分配的一种形式,按照标签逐一处理
数组的定义及其初始化
定义
定义一个空间:
1、大小
2、读取方式
声明方式:
数据类型 数组名[m] m只在申请的时候起作用
int a[100];
数组名是一个常量符号,一定不要放到等号左边;
char buf[100];
buf="hello world";
这是一个经典错误
数组同样通过 + 、-偏移,同样存在越界问题
初始化
int a[10];
a[0]=1;
a[1]=2;
...
a[9]=10;
程序这样赋值,工作量比较大,能不能让编译器进行一些自动赋值-》
空间定义时,就告知编译器初始化情况,空间第一次赋值,初始化操作
int a[10]=空间;
C语言本身,CPU内部本身一般不支持空间和空间({})的拷贝
int a[10]={10,20};====>a[0]=10;a[1]=20;a[2]=0...
--------------------------------------------------
数组空间的初始化和变量的初始化本质不同,尤其是在嵌入式的裸机开发中,空间的初始化往往需要库函数的辅助
指针和数组的经典错误
经典错误一:
char buf[10]="abc";
char *p="abc";
二者的区别:
buf[10]是buf有空间,将"abc"拷入buf变量空间;完成常量向变量的拷贝,buf可操作
buf[1]=c;是可以执行的
*p是指向abc的指针
p[1]="123";段错误
经典错误二:
char buf[]="abcd";
char buf[110]="abc";
buf="hello world";//错误
strcpy(buf,"hello world");
第二次内存的初始化,赋值:只能逐一处理
buf[0]='h';
buf[1]='e';
...
buf[n+1]="0";
字符拷贝函数的原则:
strcpy、strncpy
内存空间和内存空间的逐一赋值功能的封装
一旦空间中出现了 ’ 0 ‘ 这个值,函数就即将结束
strcpy存在严重的内存泄漏问题,转而使用strncpy函数
非字符串空间
字符空间
ASCII码编码来解码的空间
%s
\0作为结束标识
非字符空间
数据采集 0x00-0xFF 8bit
开辟数据存储这些数据盒子
char buf[10]; ----->string
unsigned char buf[10]; ------>data
无法使用strcpy的方式赋值;只能定义个数;须使用memcpy(dest,src,字节个数);
memcpy(buf,sensor_buf,10*sizeof(int));
拷贝三要素:
1、src
2、dest
3、个数
指针与数组
指针数组
char *a[100];
sizeof(a) = 100*4
二维数组和二维指针的练习与区别
int *p[5];
int (*p)[5];
int a[10];
int *p1=a;
int b[5][6];
int (*p2)[6];
结构体
字节对齐
看一下下面定义的结构体的长度
struct MyStruct
{
int a;
char b;
};
sizeof为8而不是5
原因就在于优化
牺牲掉了一些空间换取时间上的优化
32位每次读四字节,确保每次读的首位对应着内部属性的首位
最终结构体的长度一定是 4(32位系统)的倍数
内部属性位置不同也会影响结构大小
位置
//长度为12
struct MyStruct
{
char b;
int a;
short c;
};
//长度为8
struct MyStruct2
{
char b;
short c;
int a;
};
内存分布图
内存的属性
1、大小
2、在哪里
编译---->汇编----->链接
int a;
看一下int a和main的指针信息
内存分布信息:
________________________
内核空间 应用程序不允许访问
========================3G
栈空间 局部变量
========================
运行时的堆空间 malloc
========================
整个程序结束才会消失
static可将变量加入到 全局数据段
全局的数据空间{初始化,未初始化}
只读数据段 "hello world"
代码段 code
________________________
栈空间
临时的存储函数临时变量,局部变量函数返回则失效了。
函数一旦返回就释放
堆空间
运行时可以自由分配和释放的空间,生命周期是由程序员决定
分配:
malloc(),一旦成功便返回分配好的地址给我们,只需接收;对于这个地址的读方法,由程序员决定;输入参数指定分配的大小,单位是B。
存在内存泄漏的危险,使用后必须释放。
free()
char *p;
p=malloc(100);
if(p==NULL){
error;
}
free(p);
释放:
free()
只读空间
静态空间
函数
函数概述
代码的集合,用一个标签(函数名)去描述
函数与数组都是连续的内存空间,函数具备三要素:
1、函数名 (地址)
2、输入参数
3、返回值
如何使用指针保存函数?
int (*p)(int,int,char);
此时p指向函数的地址;
void TestFun(){
int (*MyMethod)(const char*,...);
MyMethod=printf;
MyMethod("======");
}
switch(day){
case 1:
fun1();
break;
case 2:
fun2();
break;
}
int (*p[7])(int,int);
p[1]=fun1();
p[2]=fun2();
p[day](10,20);
上面两种写法是相同的效果
调用函数
参数传递
调用者:
函数名(要传递的数据) //实参
被调者:
函数名(接收的数据){ //形参
实现
}
传递的实质是:**
void TestFunCanshu(int a){
printf("this is TestFunCanshu'a:%p\n",a);
}
int main(int argc,char **argv){
// printf("this is m:%p\n",main);
char *p="hello world!";
printf("the p is %p\n",p);
TestFunCanshu(p);
}
运行结果:
the p is 0x8048700
this is TestFunCanshu'a:0x8048700
值传递
void fun(int a){
a=xx;
a=sha md5复杂处理();
}
int main(){
int a=20;
fun(a);
printf a==?
}
结果:
a=20
上层调用者保护自己空间不被修改
地址传递
void TestFunChange2(int *a){
(*a)++;
}
int main(int argc,char **argv){
int a=20;
TestFunChange2(&a);
// printf("this is m:%p\n",main);
printf("a is %d\n",a);
}
结果:
a=21;
传递地址,可以修改
连续空间的传递
连续空间的传递考虑使用地址传递
数组
数组名——标签
实参:
int abc[10];
fun(abc)
形参:
void fun(int *p)
结构体
结构体变量
struct abc{
int a;
int b;
int c;
};
struct abc buf;
值传递
实参:
fun(buf);
形参:
void fun(struct abc a1){
}
**会存在两块相同大小的内存,有点浪费空间**
地址传递
实参:
fun(&buf)
形参:
void fun(struct abc *a2){
}
**工程中多使用地址传递**
连续空间的只读性
值传递:
void fun(char a);
int main(){
char a='c';
fun(a);
a=='c';
}
值传递只在相应函数内部操作
地址传递:
void fun(char *p){
//char *p该空间可以修改
p[1]='2';
};
void fun2(const char *p){
/*
const
只读空间
*/
}
int main(){
char buf[]="hello";
fun(buf);
printf("%s\n",buf);
}
返回值
看门狗
Make
Make
什么是make
make是一个命令,一个可执行程序,用来解析makefile文件的命令
这个命令存在于/usr/bin/下
什么是makefile
makefile是一个文件,此文件描述了编译规则
采用make的好处
编译时只需执行make就好,提高效率
语法规则
两个关键词:
目标、所依赖的文件列表
命令列表
- 目标
通常是要产生的文件名称,目标可以是可执行文件或其它obj文件,也可以是一个动作的名称
- 依赖文件
是用来输出从而产生目标的文件,一个目标 通常有几个依赖文件
- 命令
make执行的动作,一个规则可以含几个命令,有多个命令时,每个命令占一行
大多是gcc的编译命令
一个简单的makefile实例
main.c
#include<stdio.h>
#include "main.h"
int main(){
printf("%d\n",NUM);
return 0;
}
main.h
#define NUM 1
makefile
main=main_test
main:main.c main.h
gcc main.c -o ${main}
clean:
rm main
make命令格式
make [-f file] [target]
1.[-f file]
make默认在工作目录中找GNUmakefile \ makefile \ Makefile 的文件作为makefile输入文件。
-f可以指定以上名字以外的文件作为makefile输入文件。
2.[targets]
若使用make命令时没有指定目标,则make工具默认实现makefile文件内的第一个目标,然后退出;指定了make工具要实现的目标,则执行相应目标命令,目标可以是一个或多个(空格分隔)
makefile语法中的部分常用函数
wildcard \ notdir \ patsubst
1、wildcard : 扩展通配符
2、notdir : 去除路径
3、patsubst :替换通配符
实例
#指代当前目录下所有以.c结尾的文件名
$(wildcard *.c)