嵌入式应用工程师——C语言基础

数组

  • int a[10]={0}; 定义整型数组a,里面有10个元素
  • char ch[20]={0}; 定义字符数组ch,里面有20个字符

函数

函数的形参和实参的特点:形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的空间。

  1. 函数命名方法,驼峰命名法:单词首字母大写,例如:void FindMax(){}
  2. 外部变量、局部变量、全局变量:局部变量为在函数中定义的变量,全局变量为在函数外定义的变量,外部变量(在自定义函数中,可以采用extern 调用全局变量)

main函数

实际上,main函数可以带参数,这个参数可以认为是main函数的形参。C语言规定main函数的参数只能有2个,习惯上这两个参数写为argc和argv。arg(译:参数)
C语言还规定argc(第一个形参)必须是整型,argv(第二个形参)必须是指向字符串的指针数组。

void main(int argc, char* argv[])

static

  1. 作为变量的存储类型说明符 static int a = 0:定义静态变量,只会在程序第一次执行到该语句时被初始化,只会被执行一次,其作用域仅限于定义该变量的函数内部或者文件内部,不会被其他函数或文件所访问。
  2. 作为函数的存储类型说明符 static Function():定义静态函数,这种函数只能在定义它的源文件中被调用,其他源文件中不能调用该函数。静态函数可以避免函数名的冲突,同时也可以使得程序的安全性得到提升。(比如,当在两个文件中,定义了同一个函数名,可以使用static进行修饰函数)
  3. 作为外部变量和函数的作用域限定符: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个字节
  1. 数组指针:数组的指针。例如:int (*p)[3]; // p是一个指针,指向一个数组(数组元素的首地址),数组内是3个整型数据
  2. 指针数组:指针的数组,是一个数组,内容为指针。例如:int *p[3]; // p是一个数组,里面有3个元素,每个元素都是指针,指向整型数据
#include 

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;
}
  1. 指针的指针: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 

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 

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 

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 
#include 

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 

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 

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 

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;
}

共用体(联合体)

结构体与共用体的区别:

  1. 结构体:每个成员变量都有自己的内存地址,可以独立地被访问和修改
  2. 共用体:所有成员变量共享同一块内存空间(共用体的大小等于其最大成员的大小),因此只能同时访问一个成员变量,修改一个成员变量的值将影响其他成员变量的值。
  3. 结构体的成员变量可以同时存在多个,它们之间没有任何关系;而共用体的成员变量虽然在同一块内存中,但是它们之间的关系是互斥的,每次只有一个成员变量可以被访问

枚举

枚举只是一种基本的数据类型,不是一种构造函数,枚举值是常量,不是变量,可以让数据简洁和已读。注:第一个枚举成员的值为整型0(也可以把第一个元素定义为1),后面的成员值+1。
定义形式

enum 枚举名{枚举值表};
例如:
enum weekday{sun,mou,tue,wed,thu,fri,sat};
enum weekday a,b,c;
或者为:
enum weekday{sun,mou,tue,wed,thu,fri,sat} a,b,c;
或者为:
enum {sun,mou,tue,wed,thu,fri,sat} a,b,c;
#include 
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++编译的程序占用的内存分为以下几个部分:

  1. 栈区(stack):由编译器自动分配释放,存放函数的参数值、局部变量的值等
  2. 堆区(heap):由程序员分配释放内存,若程序员不释放,程序结束时可能由OS回收。注:与数据结构中的堆不同,分配方式类似于链表
  3. 数据区:主要包括静态全局区(static)和常量区
  4. 代码区:存放函数体的二进制代码

内存操作函数

  1. malloc()函数:memory allocation,动态内存分配,用于申请一块连续的指定大小的内存,new()也是申请动态内存
  2. calloc()函数:malloc 和 calloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。
  3. free()函数:释放内存
  4. memcpy()函数:拷贝内存
  5. memmove()函数:memove(dest,src,3) // 把src中前3个字符复制给dest
  6. memset()函数:memset(str,‘A’,4) // 把字符串的前4为设为A,注意要填写ASCII

深拷贝与浅拷贝

  • 浅拷贝:多个指针指向同一段内存;直接赋值,例如定义两个字符串,parray[i] = myarray; int a = b;这些都属于浅拷贝,浅拷贝使用比较频繁
  • 深拷贝:每个指针指向单独的内存;例如strcpy(dst,str);
    假设B复制了A,当修改A时,看B是否会发生变化。如果B也跟着变了,则是浅拷贝,如果B没变,则是深拷贝。

你可能感兴趣的:(嵌入式学习笔记,c语言,嵌入式应用工程师)