嵌入式应用工程师——C语言基础
Posted 稚子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式应用工程师——C语言基础相关的知识,希望对你有一定的参考价值。
数组
- int a[10]=0; 定义整型数组a,里面有10个元素
- char ch[20]=0; 定义字符数组ch,里面有20个字符
函数
函数的形参和实参的特点:形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的空间。
- 函数命名方法,驼峰命名法:单词首字母大写,例如:void FindMax()
- 外部变量、局部变量、全局变量:局部变量为在函数中定义的变量,全局变量为在函数外定义的变量,外部变量(在自定义函数中,可以采用extern 调用全局变量)
main函数
实际上,main函数可以带参数,这个参数可以认为是main函数的形参。C语言规定main函数的参数只能有2个,习惯上这两个参数写为argc和argv。arg(译:参数)
C语言还规定argc(第一个形参)必须是整型,argv(第二个形参)必须是指向字符串的指针数组。
void main(int argc, char* argv[])
static
- 作为变量的存储类型说明符 static int a = 0:定义静态变量,只会在程序第一次执行到该语句时被初始化,只会被执行一次,其作用域仅限于定义该变量的函数内部或者文件内部,不会被其他函数或文件所访问。
- 作为函数的存储类型说明符 static Function():定义静态函数,这种函数只能在定义它的源文件中被调用,其他源文件中不能调用该函数。静态函数可以避免函数名的冲突,同时也可以使得程序的安全性得到提升。(比如,当在两个文件中,定义了同一个函数名,可以使用static进行修饰函数)
- 作为外部变量和函数的作用域限定符:static可以限制外部变量和函数的作用域,使得他们只能在当前文件内部被访问。这样可以避免不同文件之间的变量或函数名的冲突,增强程序的可维护性。
总结:static,可以用于控制变量和函数的生命周期、作用域和访问权限,从而使得程序更加安全、易于维护。static修饰的变量只会被初始化一次,之后再运行到staic int a= 0时,会自动跳过。static修饰的局部变量相当于全局变量。
预处理命令
条件编译
#ifdef 标识符
程序段1
#else
程序段2
#endif
防止头文件被多次引用
#ifndef LED_H
#define LED_H
*
*
#endif
// 或者这样
#pragma once //需要编译器的支持。
指针
**指针(Pointer)其实就是地址,地址就是内存编号。**其一般形式为:*变量名
int* p1; // 表示p1是一个指针变量,它的值是某个整型变量的地址。
char *pc = "C Language"; // 并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量pc
/*定义一个函数*/
void Function(int *pt)
int a = *pt + 1;
// 在调用该函数时,需要传递进去变量的地址,&a
- 数组是由连续的一块内存单元组成,数组名就是这块连续内存单元的首地址。
- 通过指针引用数组元素:
- 下表法:用a[i]形式访问数组元素
- 指针法:采用*(a+i)或*(p+i)形式,用间接访问的方式来访问数组元素。*(p+5)或 *(a+5)就是a[5],因为数组名就是数组元素的首地址,相当于a[0]
char、char*、char a[]、char* a[]
C语言中规定数组代表所在内存位置的首地址.
C语言中没有String这个数据类型,一般用char *表示字符串,或者用char A[]字符数组表示字符串
C语言中操作字符串是通过它在内存中的存储单元的首地址进行的,这是字符串的终极本质。
char *s = "China";
s为一个地址,字符串常量的本质表现是它的第一个字符的地址。char *和char a[]的本质区别
:当定义char a[10]时,编译器会给数组分配10个单元,每个单元的数据类型为字符。而定义char *s时,这是个指针变量,只占4个字节,用来保存一个地址。
总结:char *s只是一个保存字符串首地址的指针变量,char a[]是许多连续的内存单元,每个元素都是char类型char *s = "hello";等价于char str[] = "hello"
字符串的表示方法
字符串没有单独的类型,所以,它可以用数组或是指针来表示。
用数组表示:
char str[] = 'b','i','t','\\0';//第一种表示方法
// 第一种写法不常用,注意加结束符 \\0
char str[] = "bit";//第二种表示方法,实际其中存储为 b i t \\0
char *parray[3]; // 存储3个字符串
用指针表示:
char *str2 = "bit";
数组指针和指针数组
前面两个字为修饰词,类型为后面的两个字。优先级:() > [] > *
- (*p)[n]:根据优先级,先看括号内,则p是一个指针,指向一个一维数组,数组长度为n,即数组的指针,数组指针
- *p[n]:根据优先级,先看[],则p是一个数组,再结合 *,这个数组的元素是指针类型,共n个元素,即指针的数组,指针数组
- 1个地址(指针)占4个字节
- 数组指针:数组的指针。例如:int (*p)[3]; // p是一个指针,指向一个数组(数组元素的首地址),数组内是3个整型数据
- 指针数组:指针的数组,是一个数组,内容为指针。例如:int *p[3]; // p是一个数组,里面有3个元素,每个元素都是指针,指向整型数据
#include <stdio.h>
int main(int argc, char *argv[])
const int max = 3;
static array[] = 1, 2, 3;
for (int i = 0; i < max; i++)
printf("array[%d] = %d\\n",i,array[i]);
int *p[max];
for (int i = 0; i < max; i++)
p[i] = &array[i];
printf("array[%d] = %p\\n",i,&array[i]);
return 0;
- 指针的指针:char **p; 相当于 char *(*p)
- 变量赋值:char *s = “C Language”; // s是一个指向字符串的指针变量,把字符串的首地址赋予给s
指针变量赋值:
| p = &a | 把变量a的地址赋值给p |
| ------------- | ------------------------------- |
| p = array | 将数组array的首地址传给p |
| p = &array[i] | 将数组array第i个元素的地址赋给p |
指针应用实例
#include <stdio.h>
int main(void)
int a = 1;
int *p = &a; // 绛変环浜庢崲涓€琛屽啓 p = &a
printf("变量a的地址为: %p\\n", p);
printf("变量a的值为: %d\\n", a);
printf("变量a的值为: %d\\n", *p);
return 0;
// ==================指针的运算
static int array[] = 1, 2, 3; // 这样每次执行时,cpu就不会再重新分配地址了
int i;
int *p = array; // 数组名本身就是一个指针,代表数组元素的首地址
for (i = 0; i < 3; i++)
printf("array[%d]= %p\\n", i, p + i);
printf("array[%d]= %d\\n", i, *(p + i)); // 输出数组的值
// 指针的指针
#include <stdio.h>
int main(int argc, char *argv[])
int a = 1;
int *p1;
int **p2; // 相当于*(*p2)
p1 = &a;
p2 = &p1;
printf("a= %d\\n", a);
printf("p1 = %p\\n", p1);
printf("p1 = %d\\n", *p1); // *表示解引用
printf("p2 = %p\\n", p2); // p2 = &p1;
printf("**p2 = %d\\n", **p2); // *表示解引用
return 0;
指针传参
// 在调用时,采用&取地址进行变量传递
#include <stdio.h>
float average(int *array, int size)
float sum = 0;
for (int i = 0; i < size; i++)
sum += array[i];
float avg = sum / size;
return avg;
int main(int argc, char *argv[])
int student[] = 10,20,30;
float avg = average(student,3); // 数组的名字就是一个指针
printf("平均值:%f\\n",avg);
return 0;
函数返回指针变量
#include <stdio.h>
#include <time.h>
int *GetNumber()
static int array[10] = 0; // C语言不支持调用函数时返回局部变量的地址,所以加一个static
int size = sizeof(array) / sizeof(int); // 数组大小的计算
srand(time(NULL)); // 随机种子
for (int i = 0; i < size; i++)
array[i] = rand();
printf("%d\\n", array[i]);
return array; // 数组名就是一个指针地址
int main()
int *p = GetNumber();
for (int i = 0; i < 10; i++)
printf("*(p+[%d]) =%d\\n ", i, *(p + i));
return 0;
结构体、联合体、枚举
结构体(struct)、联合体(union)、枚举(enum)
结构体
- 结构体指针变量的说明和使用:结构指针变量中的值是所指向的结构变量的首地址。 struct 结构名* 结构指针变量名
struct stu *pstu;
- 结构名只能表示一个结构形式,编译系统并不对它分配内存空间,结构指针变量访问成员:
(*pstu).num; // 或pstu->num
为了提高程序的运行效率,最好的办法是使用指针,即用指针变量作为函数参数进行传送,这时由实参传向形参的只是地址,从而减少了时间和空间的开销。
结构体应用实例
示例:结构体赋值
#include <stdio.h>
typedef struct
int num;
char name[20];
char sex;
int age;
float score;
char address[30];
Student;
int main()
Student stu = 0; // 结构体实例化
// stu.name = "张三"; 报错,字符串采用下面这种方法进行赋值
strcpy_s(stu.name, "张三"); // _s表示安全类型
stu.age = 1;
stu.sex = 'M'; // 字符类型赋值
return 0;
示例:结构体指针的应用(重要)
#include <stdio.h>
typedef struct
int num;
char name[20];
char sex;
int age;
float score;
char address[30];
Student;
/*采用指针的方式进行结构体实例化,调用时可以提高程序的运行效率*/
Student *stu;
int main()
stu->age = 1;
stu->sex = 'M';
strcpy(stu->name, "张三");
return 0;
示例:结构体指针形参
#include <stdio.h>
typedef struct
int num;
char name[20];
char sex;
int age;
float score;
char address[30];
Student;
Student stu;
// 如果不用指针,相当于把整个结构体传入过去,使用指针只传递地址就行,大大提升了程序的运行速度
void Print_Student(Student *pt)
printf("age = %d\\n", pt->age);
printf("name = %s\\n", pt->name);
int main()
stu.age = 1;
strcpy(stu.name, "张三");
Print_Student(&stu);
return 0;
共用体(联合体)
结构体与共用体的区别:
- 结构体:每个成员变量都有自己的内存地址,可以独立地被访问和修改
- 共用体:所有成员变量共享同一块内存空间(共用体的大小等于其最大成员的大小),因此只能同时访问一个成员变量,修改一个成员变量的值将影响其他成员变量的值。
- 结构体的成员变量可以同时存在多个,它们之间没有任何关系;而共用体的成员变量虽然在同一块内存中,但是它们之间的关系是互斥的,每次只有一个成员变量可以被访问
枚举
枚举只是一种基本的数据类型,不是一种构造函数,枚举值是常量,不是变量,可以让数据简洁和已读。注:第一个枚举成员的值为整型0(也可以把第一个元素定义为1),后面的成员值+1。
定义形式
enum 枚举名枚举值表;
例如:
enum weekdaysun,mou,tue,wed,thu,fri,sat;
enum weekday a,b,c;
或者为:
enum weekdaysun,mou,tue,wed,thu,fri,sat a,b,c;
或者为:
enum sun,mou,tue,wed,thu,fri,sat a,b,c;
#include <stdio.h>
enum DAY
MON=1, TUE, WED, THU, FRI, SAT, SUN
day;
int main()
// 遍历枚举元素
for (day = MON; day <= SUN; day++)
printf("枚举元素:%d \\n", day);
动态内存分配函数
最常用的内存管理函数是malloc,调用格式:(类型说明符*) malloc(size)
功能:在内存的动态存储区中分配一块长度为 size 字节的联系区域,函数的返回值为该区域的首地址。例如:
pc = (char*)malloc(100);
表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc
释放内存空间函数free
调用形式:free(void *ptr);
功能:释放ptr所指向的一块内存空间,被释放区应是由malloc或calloc函数所分配的区域。
动态内存管理
malloc():memory, allocation,内存分配
位域
位域的分配方式分配空间只能用在结构体和类中。
// 进行位域划分,一个int为4个字节,32bit
// 使用方法,在变量名后加一个:即可
struct Date
int year:12; //分配12个bit
int month:4; // 分配4个字节
int day:5; // ....
int hour:5;
int minute:6;
int second:6;
;
内存分区与函数类型
由C/C++编译的程序占用的内存分为以下几个部分:
- 栈区(stack):
由编译器自动分配释放
,存放函数的参数值、局部变量的值等 - 堆区(heap):
由程序员分配释放内存
,若程序员不释放,程序结束时可能由OS回收。注:与数据结构中的堆不同,分配方式类似于链表 - 数据区:主要包括静态全局区(static)和常量区
- 代码区:存放函数体的二进制代码
内存操作函数
malloc()函数
:memory allocation,动态内存分配,用于申请一块连续的指定大小的内存,new()也是申请动态内存calloc()函数
:malloc 和 calloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。free()函数
:释放内存- memcpy()函数:拷贝内存
- memmove()函数:memove(dest,src,3) // 把src中前3个字符复制给dest
- memset()函数:memset(str,‘A’,4) // 把字符串的前4为设为A,注意要填写ASCII
深拷贝与浅拷贝
- 浅拷贝:多个指针指向同一段内存;直接赋值,例如定义两个字符串,parray[i] = myarray; int a = b;这些都属于浅拷贝,浅拷贝使用比较频繁
- 深拷贝:每个指针指向单独的内存;例如strcpy(dst,str);
假设B复制了A,当修改A时,看B是否会发生变化。如果B也跟着变了,则是浅拷贝,如果B没变,则是深拷贝。
嵌入式C语言面向对象编程 --- 封装
大部分使用 C 语言进行开发的工程师,在接触更高级的编程语言之前,都认为 C 语言是面向过程的。事实也是如此,对于一些小规模的单片机应用程序,一般都是使用“面向过程”的思维进行单片机C语言编程开发。
但是,如果是需要用C语言开发一些规模比较大的软件的时候,比如操作系统内核,文件系统底层,数据库底层,等等,这个时候,就需要用面向对象的思想去考虑和设计整个软件框架了。
嵌入式Linux的内核,虽然是使用 C 语言编写的,但里面的设计大部分都使用了面向对象的编程思想。
图片来源公众号:码农翻身
很多单片机工程师或者嵌入式Linux驱动初学者,有时候会觉得驱动入门特别困难,很大一部分原因是,他们会用“过程式思维”去尝试学习驱动框架和内核框架,而非从“整体对象”的思维方向出发,这样容易导致水土不服。
任何编程语言只是一种工具,而编程思想是指导我们用好这个工具的关键。C 语言只是工具,而面向对象是一种编程思想,用来指导我们如何用从另一种思维模式去使用 C 语言。
以上是关于嵌入式应用工程师——C语言基础的主要内容,如果未能解决你的问题,请参考以下文章