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

内存管理:

堆和栈的理解和区别

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

在C语言中,内存分配方式有以下三种方式:

(1)从静态存储区域分配:

它是由编译器自动分配和释放的,即内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,直到整个程序运行结束时才被释放,如全局变量与static变量。

(2)在栈上分配:

同样是由编译器自动分配和释放的,即在执行函数时,函数内部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元将被自动释放。

(3)从堆上分配:

也称为动态内存分配,手动进行的分配。

内存分配中的栈区主要用于分配局部变量空间,处于较高的地址,其栈地址是向下增长的;

而堆区则主要用于分配程序员申请的内存空间,堆地址是向上增长的。

 堆和栈的区别:

分配的碎片问题频繁的分配和释放不同大小的堆空间会造成内存空间的不连续,从而造成大量碎片。不存在内存碎片。
分配的效率堆是由C/C++函数库提供的。操作系统有一个记录空闲内存地址的链表,当系统收到分配一块堆内存的申请时,会遍历该链表,寻找一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。栈是机器系统提供的数据结构,计算机会在底层对栈提供支持。例如:分配专门的寄存器存放栈的地址,压栈出栈都有专门的执行指令,因此栈的效率比较高。
申请的大小限制由于操作系统是用链表来存储内存地址的,同时链表的遍历方向是由低地址向高地址。因此,堆内存的申请大小受限于计算机系统中有效的虚拟内存。栈是一块连续的内存区域,栈顶的地址和最大容量一般由系统预先规定好,如果申请的空间超过栈的剩余空间,会提示溢出错误。因此,从栈获得的空间相对较小。
存储的内容根据需要存储数据。

存放函数的参数与局部变量。

在函数调用时,第一个进栈的是调用处的下一条指令(主函数中)的地址,然后是函数的各个参数(因为栈是先进后出)。

参数是由右往左入栈的,最后是函数中的局部变量(注意:static变量是不入栈的!)。

出栈时,局部变量先出栈,然后是参数,最后栈顶指针指向最开始保存的地址,也就是主函数中的下一条指令。

 最后介绍一下C语言中各类型变量的存储位置。

  • 全局变量:从静态存储区域分配。在当前的.h文件中使用extern声明后,可在其他文件中使用。
  • 全局静态变量:从静态存储区域分配。
  • 局部变量:从栈上分配。作用域只在局部函数内。
  • 局部静态变量:从静态存储区域分配。作用域只在定义它的函数内可见。


注意:在.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 保存的地址一定是定义过的(向系统申请过的)。

出现野指针的几种情况:

(1)声明未初始化的指针

int *p;
printf("%d\n", *p);  //err

(2)malloc 后 free的指针

如下,第一次printf可以输出100,第二次就错误了。因此,我们一般在free(p)后加一句 p=NULL;

int* p = malloc(sizeof(int));
*p = 100;
printf("%d\n",*p);

free(p);

printf("%d\n", *p);

(3)指针变量超出了作用域

如下,变量 a 在被调函数结束执行后就被释放了,所以p2接收到的已经不是我们需要的值了。

int* app()
{
	int a = 10;
	int* p = &a;
	return p;
}

int main()
{
	int* p2 = app();
	printf("p2 = %d\n", *p2);

	system("pause");
	return 0;
}

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 * )

(1)万能指针可以保存任意的地址。

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

(2)如果想通过万能指针操作这个指针所指向的空间的内容的时候,就要通过强制类型转换将这个万能指针转换一下,例如:

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

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

(3)

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

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

(4)不同类型的指针之间,需要强制转换才不会报警告。但是,万能指针不通过强制类型转换,就可以转为任意类型的指针。

int main()
{
	void* p = NULL;
	int* pInt = NULL;
	char* pChar = NULL;

	pChar = (char*)pInt;    //需强制类型转换
	pChar = p;              //不需要转换

	system("pause");
	return 0;
}

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)数组作为函数的参数

  • 数组作为函数的形参会退化为指针。 
  • 数组作为函数的参数时,必须再用另一个参数来传入数组的大小。

eg1: 

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

eg2:找出数字key在数组a中的位置。

int search(int key, int a[], int length)
{
    int ret = -1;
    for (int i = 0; i < length; i++)
    {
        if (a[i] == key)
        {
            ret = i;
            break;
        }
    }
    return ret;
}

int main()
{
    int a[] = {1,2,3,4,52,64,18,9,0};
    int x;
    int loc;
    printf("请输入一个数字:\n");
    scanf("%d", &x);
     loc = search(x, a, sizeof(a)/sizeof(a[0]));
    if (loc != -1)
    {
        printf("数字%d在数组中位于第%d个位置上。\n", x, loc);
    }
    else
    {
        printf("不存在\n");
    }

    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[] = "hello";
    char* p = a;
    printf("%s\n", p);       //%s:打印一个字符串,要的是首个字符的地址
    printf("%s\n", p + 2);
    printf("%c\n", *p);
    printf("%c\n", *(p + 2));
    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof(p));
    printf("%d\n", strlen(a));
    printf("%d\n", strlen(p));
    *p = 'm';
    printf("%s\n", p);
    p++;
    *p = 'n';
    printf("%s\n", p);
}

 输出为:

hello
llo
h
l
6
4
5
5
mello
nllo

17.1 字符串常量

字符串常量存放在文字常量区,文字常量区的内容不可以改变。

但是可以创建一个字符数组,并使用字符串字面量初始化它,这意味着字符串的内容被复制到了数组中。此时执行*p = 'm'; 时,实际修改的是数组a的第一个元素,而不是修改原始的字符串字面量。

int main()
{
    //定义了一个字符数组,字符数组的内容是helloworld。a[]是局部变量,存储在栈区
    char a[] = "helloworld"; //此时已经在栈区开辟了空间,所以helloworld存在栈区里

    char* p = a;
    *p = 'm';    //这里可以更改p指向的地址的内容,因为p指向的字符串是在栈区
    printf("%s\n", p); //输出为:melloworld

    p = "abcd";   //"abcd"是字符串常量,存储在文字常量区,这里是把字符串常量的首元素地址赋值给了p
    printf("%s\n", p);  //输出为:abcd
    
    *p = 'm';     //err,此时p指向的是文字常量区,文字常量区的内容是不能改变的
    printf("%s\n", p);
    
    system("pause");
    return 0;
}

总结一下:

  • 字符串字面量 "helloworld" 是不可修改的(通常)。
  • 字符数组 a 是可以修改的,并且使用字符串字面量来初始化它时,字符串的内容会被复制到数组 a 中。
  • 指针 p 指向数组 a 的首地址,因此 *p 实际上指的是 a[0],你可以修改它。

18. 字符指针作为形参

char* my_strcat(char* src, char* dst)
{
    int n = strlen(src);
    int i = 0;
    while (*(dst + i) != 0)   //0等价于'\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修饰的指针变量

const char* p
char* const p

从左往右看,跳过类型,看修饰哪个字符:

如果是 *,说明指针指向的内存不能改变;

如果是指针变量,说明指针的指向不能改变,指针的值不能修改。

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, char* 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]。

(9)输出为:100

本题涉及的几个点:① 用memcpy(目标地址,源地址,长度)函数将a拷贝至buf中;② 用指针p指向数组buf的地址;③ 将char*类型的p强制转换为int *类型,然后再加*,用来表示完整的值a。

int main()
{
	char buf[1024] = { 0 };
	int a = 100;
	memcpy(buf, &a, sizeof(int));

	char* p = buf;
	printf("%d\n", *(int*)p);

	system("pause");
	return 0;
}

(10)指针做函数参数的输入特性:在主调函数分配内存

包括:① 栈上分配内存;② 堆上分配内存。

void func(char* p)
{
	strcpy(p, "hello world");
}

void test_1()
{
	//在栈上分配内存
	char buf[1024] = { 0 };
	func(buf);

	printf("%s\n", buf);
}

void printString(char* str)
{
	printf("%s\n", str);
}

void test_2()
{
	//在堆区分配内存
	char* p = malloc(sizeof(char) * 64);
	memset(p, 0, 64);
	strcpy(p, "hello world");

	//在被调函数中 输出helloworld
	printString(p);

    free(p);
    p = NULL;
}

int main()
{
	test_1();
	test_2();

	system("pause");
	return 0;
}

输出为:

hello world
hello world

(11)指针做函数参数的输出特性:在被调函数分配内存,分配在堆区内存

void allocateSpace(char** p)
{
	char* temp = malloc(sizeof(char) * 64);
	memset(temp, 0, 64);
	strcpy(temp, "hello world");

	*p = temp;
}

void test_3()
{
	char* p = NULL;
	allocateSpace(&p);

	printf("%s\n", p);
    
    free(p);
    p = NULL;
}

int main()
{
	test_3();

	system("pause");
	return 0;
}

(12)

int main()
{
	char str[] = "hello\012world";
	printf("%s\n", str);
	printf("sizeof str:%d\n", sizeof(str));
	printf("strlen str:%d\n", strlen(str));

	system("pause");
	return 0;
}

\012 为八进制数字,转为十进制为10,对应ASCII码里的换行。

则输出为:

hello
world
sizeof str:12
strlen str:11

23. 实现字符串拷贝的三种方法:

如下代码,三种方式输出均为 hello world

void copyString01(char* des, char* src)
{
	//利用下标方式拷贝
	int len = strlen(src);
	for (int i = 0; i < len; i++)
	{
		des[i] = src[i];
	}
	des[len] = '\0';
}

void copyString02(char* des, char* src)
{
	//利用字符串指针
	while (*src != "\0")
	{
		*des = *src;
		des++;
		src++;
	}
	*des = '\0';
}

void copyString03(char* des, char* src)
{
	while(*des++ = *src++)
	{ }
}

void test()
{
	char* str = "hello world";
	char buf[1024];
	copyString01(buf, str);
	//copyString02(buf, str);
	//copyString03(buf, str);
	printf("%s\n", buf);
}

int main()
{
	test();

	system("pause");
	return 0;
}

24. 字符串反转

void reverString1(char* str)
{
	int len = strlen(str);
	int start = 0;
	int end = len - 1;
	while (start < end)
	{
		char tmp = str[start];
		str[start] = str[end];
		str[end] = tmp;
		start++;
		end--;
	}
}

void reverString2(char* str)
{
	int len = strlen(str);
	char* start = str;
	char* end = str + len - 1;

	while (start < end)
	{
		char tmp = *start;
		*start = *end;
		*end = tmp;
		start++;
		end--;
	}
}

void test()
{
	char str[] = "abcd";
	reverString1(str);
	reverString2(str);
}

int main()
{
	test();

	system("pause");
	return 0;
}

 25. 移位运算符

左移:num<<n, 等于num乘以2的n次幂

右移:num>>n, 如果num非负,则num除以2的n次幂;如果为负,分机器。

void test_05()
{
	int num1 = 20;
	int num2 = 10;
	printf("num1 <<= 3 = %d\n", num1 <<= 3);
	printf("num2 >>= 1 = %d\n", num2 >>= 1);
}

int main()
{
	test_05();

	system("pause");
	return 0;
}

输出为:

num1 <<= 3 = 160
num2 >>= 1 = 5

26. 指针越界

void test()
{
    char buf[3] = "abc";
    printf("buf:%s\n",buf);
}

输出为:

buf:abc烫烫?
郳曾?

27. 指针释放时容易出错的地方

指针进行叠加运算时,会不断改变指针的指向,此时释放原指针的话会出错。解决方法是:创建一个临时指针指向原指针,并操作这个临时指针,最后释放原指针的话就不会出错。

如果递增的是原指针p,实际上就丢失了对原始分配的内存块起始地址的引用。这意味着在循环结束后,p指向了内存中一个新的、未知的位置,而不是最初malloc分配的内存块的起始地址。

void test()
{
    char* p = malloc(sizeof(char) * 64);
    char* temp = p;   //使用temp指针来遍历内存块
    for (int i = 0; i < 3; i++)
    {
        *temp = i + 97;    //修改temp指针当前指向的字符
        printf("%c\n", *temp);
        temp++;
    }
    if (p != NULL)
    {
        free(p);    //使用p指针(原始地址)来释放内存
        p = NULL;
    }
}

int main()
{
	test();

	system("pause");
	return 0;
}

输出为:

a
b
c

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值