IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 嵌入式C语言基础知识查漏补缺--内存管理&函数&指针&数组 -> 正文阅读

[C++知识库]嵌入式C语言基础知识查漏补缺--内存管理&函数&指针&数组

内存管理:

局部变量静态局部变量全局变量静态全局变量
作用域在定义变量的{}之内有效在定义变量的{}之内有效整个工程,所有文件当前文件
生命周期程序运行至变量定义处开辟空间,所在的函数结束之后释放空间执行main函数之前就已经开辟空间,程序结束之后才释放空间执行main函数之前就已经开辟空间,程序结束之后才释放空间执行main函数之前就已经开辟空间,程序结束之后才释放空间
未初始化的值随机000


注意:在.h中,全局变量只声明不定义,定义只放在.c文件。

?memset:

memcpy:

memcmp:

malloc:

free:

数组?

1. 计算机的最小存储单位是字节Byte,共有8个bit构成,不管是32位还是64位CPU。

2. break:强行从循环体跳出,执行循环语句块后面的语句;

? ? continue:从continue点返回循环体条件判断,继续进行下一次循环动作。

3. int *p:定义p指针变量,该变量用来指向一个地址,该地址存放的是一个int型数据。

4. 一维数组

eg:int a[5];

  • a[0]:? ? ? ?第 0 个元素
  • &a[0]:? ? 第 0 个元素的地址
  • 数组名 a :代表数组,也代表第0个元素的地址,等于&a[0]。所以说数组名是一个常量,是一个地址,不能被赋值。
  • &a :? ? ? ? 数组的地址,在数值上,&a[0],a,&a三者相等。
  • &a[0]+1: 元素的地址加1,表示跨过一个元素
  • a+1:? ? ? ? a = &a[0],所以也表示跨过一个元素
  • &a+1:? ? ?整个数组的地址加1,表示跨过一个数组

5. 二维数组

eg:int a[3][4]

  • 定义了一个三行四列的数组,数组名为a,元素类型为整型。
  • 二维数组a的元素是按行进行存放的。即先存完第0行,再存下一行。
  • 一维数组的a[0]代表第0个元素,二维数组的a[0]代表第0行。
  • a[0][0]:? 第0行第0个元素
  • &a[0][0]:第0行第0个元素的地址
  • a[0]:? ? ? 代表第0行这个一维数组的数组名,a[0] =?&a[0][0]
  • &a[0]:? ? 第0行的地址
  • a:? ? ? ? ? 二维数组的数组名,代表二维数组,也代表首行地址,a = &a[0]
  • &a:? ? ? ? 二维数组的地址
  • &a[0][0] + 1 :元素地址加 1,跨过一个元素
  • a[0] + 1 :? ? ? 元素地址加 1,跨过一个元素
  • &a[0] + 1 :? ? 行地址加 1,跨过一行
  • a + 1:? ? ? ? ? ? 行地址加 1,跨过一行
  • &a + 1:? ? ? ? ?二维数组地址加 1,跨过整个数组

6.?字符数组

int a[10]   //每个元素是int类型,所以这个是数值数组
char a[10]  //每个元素是char类型,所以这个是字符数组

字符串就是字符数组中有 \0?字符的数组。

eg:

①?普通的字符数组:

char a[5] = {'a','b','c','d','e'};

②?字符数组中含有 \0?字符,它也是个字符串:

char a[5] = {'a','b','c','d','\0'};

③?定义了一个字符数组,存的是?abcd\0:

char a[5] = "abcd";

④?定义了一个字符数组,有100个元素:

char a[100] = "abcd";

⑤?将数组的第0个元素填 \0,其他元素就是 \0:

char a[100] = "\0";

⑥?将一个字符数组清0:
?

char a[100] = {0};

字符数组与字符串的区别:

  • C语言中没有字符串这种数据类型,可以通过 char 的数组来替代;
  • 字符串一定是一个?char 的数组,但 char?的数组未必是字符串;
  • 数字0(和字符'\0'等价)结尾的 char?数组就是一个字符串,但如果?char?数组没有以数字0结尾,那么它就不是一个字符串,只是普通的字符数组。?

⑦ 普通字符数组,如果用 %s?打印的话(%s?是指输出字符串格式,从字符数组的起始地址,即首元素地址开始打印,直至出现 \0?为止),没有 \0?结束符的话会出现乱码。

函数

1.?函数定义时,()里面的参数叫形参,因为这个形参只是形式上的参数,在定义函数时没有给形参开辟空间。形参只有在被调用时才会分配空间。

2.?调用函数时,()里面的参数叫做实参,实参的类型和形参的必须一致、个数必须相同。

3.?头文件中只声明,不定义;只在 .c?中定义。

4.防止头文件重复包含:

#ifndef?宏(宏的名字最好和文件相同,大写)

#define?宏

#endif?

② #pragma?once

5. 静态函数:函数定义时加上static修饰的函数。静态函数只能被当前文件函数调用。

指针

5.?定义指针:?用 *p?替换掉定义的变量。

eg:int a,把 a 用 *p 代替,得到 int *p。

6.?int *p:与 *?结合代表这是一个指针变量;

p 是变量,p?的类型是:将变量p本身拖黑,剩下的类型就是指针变量的类型,即?int *

?指针变量?p?用来保存什么类型的数据的地址:将指针变量?p?和最近的一个 *?一起抹黑,剩下的就是保存的数据的类型,eg:int **p,保存的是?int *?数据类型。

7.?指针的宽度 =?sizeof(将指针变量和指针变量最近的 *?拖黑,剩下的长度)

宽度也叫步长,即指针加 1?跨过多少个字节。

指针加 1 ,则跨过一个步长。

eg:

char *p1
short *p2
int *p4
int **psizeof(int *) = 4

8.?野指针就是没有初始化的指针,指针的指向是随机的,不可以操作野指针。

指针?p?保存的地址一定是定义过的(向系统申请过的)。

9.?空指针,就是将指针的值赋值为0,即 null。eg:int *p = NULL。

空指针的作用:如果使用完指针,将指针赋值为null,在使用时判断一下指针是否为null,就知道指针有没有被使用。

看下面的代码:

int main()
{
    
    int *p = NULL;      //给指针的值赋值为0,即null
    *p = 200;           //err,因为 p 保存了0x0000的地址,这个地址是不可以使用的  
    printf("%d\n", *p);

    system("pause");
    return 0;
}

10.?万能指针( void * )

万能指针可以保存任意的地址,但是如果想通过万能指针操作这个指针所指向的空间的内容的时候,就要通过强制类型转换将这个万能指针转换一下,如下所示。

int main()
{
    int a = 10;
    short b = 20;

    void* p = (void*)&a;      //万能指针可以保存任意的地址,需要先把指向的地址转换为void *类型
    void* q = (void*)&b;

    //printf("%d\n", *p);          //err,p是void *,不知道取几个字节的大小
    printf("%d\n", *(int*)p);      //强制类型转换:由 “ * 地址 ”转换为“ *((int *)地址) ”
    printf("%d\n", *(short*)q);

    system("pause");
    return 0;
}

输出为:

10
20

另外:

①?void?b;? //错误,不可以定义void类型的变量,因为编译器不知道给变量分配多大的空间。

但是可以定义void *类型,因为指针都是4个字节。

②?? ? printf("%d\n", *(int*)p); ? ? ?//强制类型转换:由 “ * 地址 ”转换为“ *((int *)地址) ”

这个语句中,p?就是a的地址,但是p的类型是void *类型。为了取到4个字节的a的值,先使用强制类型转换将地址类型转换成4个字节的int *类型,然后取int *类型的地址里的内容,即a的值。

11.?const修饰的指针

看const修饰的是 *?还是?p。

(1)修饰 *,不能通过 *p?修改p所指向的空间的内容。

const int *p = &a;
*p = 100;          //err

(2)修饰变量?p,则 p?保存的地址不可以修改。

int *const p = &a;
p = &b;          //err

(3)p?本身的指向不能改变,且不能通过 *p?修改p指向空间的内容。

const int *const p = &a;

12.?通过指针操作数组元素

指针加1,跨过一个步长。

int *p;?步长 =?sizeof(int)?

int main(int argc, char const *argv[])
{
    int a[10] = {0};
// a 表示数组名、首元素地址
    int *p = a;   // 指针 p 保存的是首元素的地址
    for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    {
        *(p + i) = i;  //指针加1,则跨过一个步长
    }
    for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    {
        printf("%d ", a[i]);
    }



    system("pause");
    return 0; 
}

13.?指针运算

两指针相加没有意义;两指针相减(类型一致),得到的是中间跨过多少个元素,看下边的代码:

int main(int argc, char const *argv[])
{
    int a[5] = { 1,2,3,4,5 };
    int* p = a;
    int* q = (int *)(&a + 1) - 1;
    printf("%d\n", *(p + 3));
    printf("%d\n", q - p);

    system("pause");
    return 0; 
}

输出为:

4
4

14.?如下代码所示,四个?printf?表示的含义一样。即,[ ] == *() 。

int main(int argc, char const *argv[])
{
    int a[5] = { 1,2,3,4,5 };
    int* p = a;

    for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    {
        printf("%d ", a[i]);
        printf("%d ", *(p + i));
        printf("%d ", p[i]);
        printf("%d ", *(a + i));
    }

    system("pause");
    return 0; 
}

15.?指针数组

整型数组:数组的每一个元素都是整型

指针数组:数组的每一个元素都是一个指针

eg:int *arr[3] = {&a, &b, &c};

16.?指针与函数

(1)指针作为函数的形参,可以改变实参的值。

eg:交换两个数

void swap(int *x, int *y)
{
    int k = *x;
    *x = *y;
    *y = k;
    printf("x = %d y = %d\n", *x, *y);
}

int main(int argc, char const *argv[])
{
    int a = 10;
    int b = 20;
    swap(a, b);
    printf("a = %d b = %d\n", a, b);

    system("pause");
    return 0; 
}

(2)数组作为函数的参数

数组作为函数的形参会退化为指针。?

void print_arr(int *b, int len)
{
    int n = sizeof(b) / sizeof(b[0]);  //b是int*类型,b[0]是int类型,则n=4/4=1
    printf("n = %d\n", n);
    for (int i = 0; i < len; i++)
    {
        printf("%d ", b[i]);
    } 
    printf("\n");
}

int main()
{
    int a[5] = {1,2,3,4,5};
    print_arr(a, sizeof(a) / sizeof(a[0]));  //这里传的是首元素的地址,所以函数里也应该定义一个int*类型来接。

    system("pause");
    return 0;
}

(3)指针作为函数的返回值

局部变量的空间在函数结束后会被释放,所以这里把num定义为全局变量,就可以返回其地址。

int num = 0;
int *get_num()
{
    srand(time(NULL));
    num = rand();
    return &num;      //局部变量的空间在函数结束后会被释放,所以这里把num定义为全局变量,就可以返回其地址。
}

int main()
{
    int *p = get_num();
    printf("%d\n", *p);

    system("pause");
    return 0;
}

习题:

char arr[] = "hello world";
char *p = arr;
int i = 0;
for(; i<sizeof(arr)/sizeof(char); ++i)
{
    printf("%c", p[i]);
}

输出为:hello?world

for循环中 ++i?和?i++的区别:结果都是一样的,都要等代码块执行完毕后才能执行语句3。

17.?字符串常量

int main()
{
    char a[] = "helloworld";  //定义了一个字符数组,内容为helloworld
    char* p = a;              //定义了一个指针用来保存数组首元素的地址
    p = "abcd";               //字符串常量存在文字常量区,“”在使用时,取的是字符串首元素的地址

    *p = 'm';  //err,文字常量区的内容是不可以改变的

    system("pause");
    return 0;
}

18.?字符指针作为形参

char* my_strcat(char* src, char* dst)
{
    int n = strlen(src);
    int i = 0;
    while (*(dst + i) != 0)
    {
        *(src + n + i) = *(dst + i);
        //等价于src[n+i] = dst[i];
        i++;
    }
    *(src + n + i) = 0;    //字符串结尾加 \0
    return src;
}

int main()
{
    char str1[128] = "hello";
    char str2[128] = "12345";
    printf("%s\n", my_strcat(str1, str2));   //链式调用,上一个函数的返回值作为下一个函数的参数

    system("pause");
    return 0;
}

19.?const修饰的指针变量

int main()
{
        char  buf[] = "hello";
        char  str[] = "acbg";
        const char  *p = buf;//const修饰指针,不能通过指针修改指针所指向的空间内容
        //*p = 'b';  err  不能通过p指针修改那块空间的内容
        char  * const  k = buf;//指针变量k初始化之后不能改变k指针变量本身的指向
//      k = "world"; err 
//      k = str; err

        system("pause");
        return 0;
}

20.?字符指针数组

是一个数组,每一个元素是字符指针。

int main()
{
    char* num[3] = { "haha","hehe","xixi" };
    char** p = num;
    //定义一个指针保存num首元素的地址,首元素num[0]是char*类型,则 char* 的地址是char**。
    //因此 p 表示num[]中首元素的地址,即&num[0];
    //*p 表示num[0]的值,即"haha"的地址;
    // p+1 表示&num[1],则*(p+1)表示num[1]的值,即"hehe"的地址;
    //*(*(p+1)+1)表示"hehe"中第一个e;

    for (int i = 0; i < 3; i++)
    {
        printf("%s\n", p[i]);
    }
    printf("%c\n", *(*(p + 1) + 3)); // == *(p[1]+3) == p[1][3]

    system("pause");
    return 0;
}

输出为:

haha
hehe
xixi
e

下图即为上述代码所示。

?21.?字符指针数组作为main函数的形参

int main(int argc, cahr* argv[ ])

argc是执行可执行程序时的参数个数;

argv是一个字符指针数组,保存的是参数(字符串)的首元素地址。

22.?习题

(1)

       void sum(int *a)
       { 
           a[0]=a[1];
       }
       main( )
       {
           int aa[10]={1,2,3,4,5,6,7,8,9,10},i=0;
           for(i=2;i>=0;i--)  
              sum(&aa[i]);
           printf("%d\n",aa[0]);
       }

输出为:4。

解析:第一次调用sum()函数时,传入的是aa[2]的地址,那么sum()函数里的 a 就指向了aa[2],a[0]即等于aa[2],a[1]即等于aa[3]。

(2)

char s[10];
s = "abcd";

解析:错误。s?是数组名,是个常量,不能被赋值。

(3)

若有下面的变量定义,则以下语句合法的是:

int i=0, a[10]={0}, *p=NULL;
A. p=a+2  B. p=a[5]  C. p=a[2]+2  D.p=&(i+2)

解析:A

A:合法。a代表首元素地址,a+2代表a[2]的地址;

B、C:等号左边p为int*类型,等号右边为int类型;

D:i+2是常量,对常量不能取地址。

(4)

下面程序的输出结果是:

int f(char* s)
{
    char* p = s;
    while (*p != '\0')
    {
        p++;
    }
    return(p - s);
}

int main()
{
    printf("%d\n", f("HENAN"));

    system("pause");
    return 0;
}

解析:5

流程:主函数调用了f()函数,把字符串传了进去,则s就指向了首元素的地址;然后,p也指向了首元素的地址;while里判断如果没遇到字符串结束符,则p+1;直到最后即为求字符串的长度。

(5)

void fun(char s1[])
{
    int i, j;
    for (i = j = 0; *(s1 + i) != '\0'; i++)
    {
        if (*(s1 + i) < 'n')    //*(s1+i)等价于s1[i]
        {
            *(s1 + j) = *(s1 + i);
            j++;
        }
    }
    *(s1 + j) = '\0';
}

int main()
{
    char str[]="morning",*p;
    p = str;
    fun(p);
    puts(p);

    system("pause");
    return 0;
}

解析:mig

①?数组作为函数的形参时,会退化为指针,即 char s1[] 等价于 char *s1,p指向首元素的地址,则s1也指向首元素的地址;

②?当 s1[i] < 'n' 时,则把?s1[i]?的值赋给?s1[j],然后j++,i++,继续循环。

(6)

下面程序的输出结果是:

int main()
{
    char str[]="abc\0def\0gh";
    char* p = str;
    printf("%s\n",p+5);
    printf("helloxx\0xxworld\n");
    printf("\n------------\n");
    printf("hello%sworld\n", "xx\0xx");

    system("pause");
    return 0;
}

输出为:

ef
helloxx
------------
helloxxworld

解析:

printf()?输出字符串时,遇到第一个 \0?即结束。

(7)

void fun(char* c, int d)
{
    *c = *c + 1;
    d = d + 1;
    printf("%c,%c,", *c, d);
}

int main()
{
    char a = 'A', b = 'a';
    fun(&b, a);
    printf("%c,%c\n", a, b);

    system("pause");
    return 0;
}

输出为:b,B,A,b

解析:传入地址的话,会改变值。

(8)

int a = 2;
int f(int* a)
{
    return (*a)++;
}

int main()
{
    int s = 0;
    int a = 5;
    s += f(&a);
    s += f(&a);
    printf("%d\n", s);
}

输出为:11

解析:

main()?里调用的f()函数,传入的是局部变量a的地址;

return(*a)++,先返回*a,即局部变量a的值,再执行++,即a+1。

同理,看下边这个题:

int main()
{
    int a[5] = {1,3,5,7,9};
    int *p = a;
    printf("%d\n", (*p)++);
    printf("%d\n", *(p++));
    printf("%d\n", *p++);
    printf("%d\n", *p);
}

输出为:1 2 3 5

解析:? ? ?printf()?函数的计算是从右往左的。

第一个:和上边一样,先输出*p,即a[0]的值,然后再执行 *p+1,即a[0] = a[0]+1 = 2;

第二个:此时?p?还是指向a[0],当执行 *(p++) 时,从右往左执行,看到有?p++,还是先执行输出 *p,再进行++。这条语句执行完后输出为2,p指向a[1];

第三个:运算符优先顺序,*和++同等优先级,则根据printf()从右往左的执行顺序,*p++?= *(p++)。这条语句执行完后输出为3,p指向a[2];

第四个:输出a[2]。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-28 22:43:27  更:2021-12-28 22:44:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:43:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码