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

静态

应用场景

修饰三种数据:

  1. 函数内部的变量

int fun(){

  int a;  ===>  static int a;

}

  1. 函数内部的变量

int a; ===> static int a;

int fun(){

}

  1. 函数

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)
Makefile中的$@, $^, $< , $?, $%, $+, $*

https://blog.csdn.net/dlf1769/article/details/78997967

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值