C语言基础08——程序调试。断言、const讲解、strcpy()函数模拟实现、调整奇偶数顺序

目录

以下程序发生死循环

字符串拷贝

断言的使用

const修饰指针变量

继续优化我们写的strcpy函数

strcpy优化最终版

自己编写strlen

错误出现的类型、Debug与Release的区别

编写方法,调整奇偶数顺序


以下程序发生死循环

/*
 * - i 和arr都是局部变量,局部变量存放在栈区上。
 * - 栈区内存的使用习惯是:先使用高地址空间,再使用低地址空间
 * - 数组随着下标的增长地址是由低到高变化的。
 *
 * - 如果是先定义的i,再定义的数组。那么数组越界就可能会访问到i变量。就可能造成死循环
 *   VC6.0h环境,数组后面就是i变量地址
 *   gcc环境,数组后面过一个整型,才是i变量的地址
 *   vs2013,数组与i变量地址中间,是两个整型
 */

#include <stdio.h>

int main()
{
    //因为这里是先定义i变量,所以i在高地址。后定义的arr,arr数组的下标如果越界,则会访问到i变量所在的地址
    //这里使用的是gcc编译器,数组过后一个整型,就是i变量。也就是下标为11的就是i变量所在地址。
    //每次当i=11时,arr[11]=0,此时就会死循环。
    long i = 0;
    long arr[16] = {1,2,3,4,5,6,7,8,9,1};
    for(i=0; i <= 17 ; i++)
    {
        arr[i] = 0;
        printf("hehe\n");
    }
    return 0;
}

我们自己调试代码一般都是在Debug模式下,但提交的时候是在Release模式下的,所以Debug模式下没有出现的问题,Release模式下可能出现。

#include <stdio.h>

int main()
{
    int i = 0;
    int arr[10] = {0};
//    for(i=0 ; i<=12 ; i++)
//    {
//        arr[i] = 1;
//        printf("haha\n");
//    }
    //Debug模式下不做优化,i的地址在数组地址的后面,相差很少,一旦越界,就可能造成以上程序死循环。
    //printf("%p\n",&i);  //0000008b755ffcac
    //printf("%p\n",&arr[9]); //0000008b755ffca4

    //Release模式下会做优化,i的地址放到了数组地址的前面,并且相差很多,就算数组越界,也不会造成程序死循环。
    printf("%p\n",&i);  //000000226cbffd0c
    printf("%p\n",&arr[9]); //000000226cbffd34
}

字符串拷贝

#include <stdio.h>

void myStrCpy(char* dest,char* src)
{
    //当不是字符串结尾的时候拷贝
    while(*src != '\0')
    {
        *dest++ = *src++;
    }
    //执行到这里,*src中存储的就是\0了,*dest也++了,虽然此时循环停止了,但是我们可以手动拷贝\0到dest中
    *dest =*src;
}
int main()
{
    char arr1[20] = "abcdefgh";
    char arr2[] = "hello";
    myStrCpy(arr1,arr2);
    printf("%s",arr1);
    return 0;
}

简化

#include <stdio.h>

void myStrCpy(char* dest,char* src)
{
    //当没有到达字符串结尾的时候,循环一直进行。这里是后置++,先赋值再运算。
    //()中表达式的结果就是*src的结果。例如:b=3,a=b这个表达式的结果应该是a,但是a=b,也就是说b就是这个表达式的结果。
    //当src指向的是'h'时,它对应的ASCII码是:104。非零条件为真,执行空语句。此时后置++生效,继续判断()中的结果
    //只有当src='\0'的时候,也就是dest='\0',字符串结尾符'\0'拷贝了过去,字符串拷贝完成。而'\0'的ascii码就是0,也就是条件为假,循环结束。
    while(*dest++ = *src++);

    //参考:
    //int i = 1;
    //printf("%d\n",i='\0'); //0
}
int main()
{
    char arr1[20] = "abcdefgh";
    char arr2[] = "hello";
    myStrCpy(arr1,arr2);
    printf("%s",arr1);
    return 0;
}

断言的使用

#include <stdio.h>
#include <assert.h>

void myStrCpy(char* dest,char* src)
{
    //当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。
    //当传入的两个数组中有一个是空指针,则输出错误信息
    assert(dest != NULL);
    assert(src != NULL);
    while(*dest++ = *src++);

}
int main()
{
    char arr1[20] = "abcdefgh";
    char arr2[] = "hello";
    myStrCpy(arr1,arr2);

    //myStrCpy(arr1,NULL);当传入NULL,则会报错
    //Assertion failed: src!=NULL, file D:/Program Files (x86)/CLion 2021.3.1/Project/First/pointer.c, line 42

    printf("%s",arr1);
    return 0;
}

const修饰指针变量

#include <stdio.h>

int main()
{
    int nu = 66;
    //const修饰的变量,被称为常变量,不能被修改。但其本质上还是变量,只是增加了不可被修改的属性。
    const int num = 10;

    //在使用const修饰之后,num无法被直接修改了,修改会报错:assignment of read-only variable 'num'
    //num = 30;

    //但是我们取地址放到指针中后,就可以修改。
    //int* p = &num;
    //*p = 20;

    //p是指针变量,*p是指针变量指向的变量。
    //const修饰指针变量的时候,如果const是放在*的左边,修饰的是*p,表示指针指向的内容,不能通过指针来改变的。
    //const int * p  与int const * p使用起来没有差异。只要是在*的左边就是修饰*p的
    //const int* p = &num;
    //*p = 20; //不能通过指针来修改

    //p = &nu;//指针变量指向哪里是可以修改的。原来指向num变量,现在可以指向nu变量
    //*p = 200; //但仍然不可以使用*p,报错:assignment of read-only location '*p'
    //printf("%d\n",nu);

    //const修饰指针变量,如果是放在*的右边,修饰的是指针变量p,表示指针变量不能修改指向哪里。
    //int * const p = &num;
    //p = &nu;//指针变量指向哪里是不可修改的。原来指向num变量,就只能指向num变量。不能再指向其他变量。
    //*p = 40;  //但是可以修改其指向的变量的内容。
    //printf("%d\n",num);

    //如果*左边和右边都有const了,*左边的const修饰*p,表示指针变量指向的变量不可以修改。*右边的const修饰p,表示p不能修改指向的位置。
    const int * const p = &num;
    //*p = 20;
    //p = &nu;

    //但如果是将其指针变量的地址取出来,保存到一个指针变量中,那么通过二级指针就可以修改p与*p。
//    int** pp = &p;
//    ** pp = 555;
//    printf("%d\n",num);
//      //可以修改指向哪个变量
//    *pp = &nu ;
//    **pp = 666;
//    printf("%d\n",nu);

    //如果是二级指针使用const,则最左边的const是修饰**pp的,中间的const是修饰*pp的,最右边的const是修饰pp的。
    int const * const * const pp = &p;

    //三级指针以此类推......
    return 0;
}

继续优化我们写的strcpy函数

#include <stdio.h>
#include <assert.h>

//库函数中的字符串拷贝函数是char * strcpy ( char * destination, const char * source );
//它的形参在源数组前加上了一个const,为什么要加这个东西呢?
//我们拷贝是要把src指向的内容放入dest指向的空间中,从本质上讲,是希望dest指向的内容被修改,而src指向的内容不修改。
//也就是*src不能被修改,所以在*的左边加上了const,表示*src不能被修改。
void myStrCpy(char* dest,const char* src)
{
    assert(dest != NULL);
    assert(src != NULL);
    //const表示被修饰的变量不可被修改。防止我们在编写的过程中写反。
    // 也就是说如果我们这里写反了,那么编译都不会过去,报错:assignment of read-only location '*src++'
    //while(*src++ = *dest++);
    while(*dest++ = *src++);

}
int main()
{
    char arr1[20] = "abcdefgh";
    char arr2[] = "hello";
    myStrCpy(arr1,arr2);
    printf("%s",arr1);
    return 0;
}

strcpy优化最终版

#include <stdio.h>
#include <assert.h>

//库函数中的字符串拷贝函数是char * strcpy ( char * destination, const char * source );
//库函数返回类型是一个指针,返回的是目标的起始地址。
char* myStrCpy(char* dest,const char* src)
{
    assert(dest != NULL);
    assert(src != NULL);
    //我们定义一个指针,接收传入函数的目标起始地址,保存到变量中。
    //使用ret进行拷贝地址的变化。
    char* ret = dest;
    while(*ret++ = *src++);
    //拷贝完成后返回起始地址。
    return dest;
}
int main()
{
    char arr1[20] = "abcdefghjklmn";
    char arr2[] = "hi baby!";
    printf("%s",myStrCpy(arr1,arr2));
    return 0;
}

自己编写strlen

//是求其长度,所以*str不可以被修改。
//这里可以设置为size_t类型,也就是unsigned int,无符号整型
size_t myStrLen(const char* str)
{
    size_t count = 0;
    assert(str != NULL); //也可以写成assert(str),因为字符串如果为空的话,就是'\0',0就是假。
    while(*str++ != '\0')
    {
        count++;
    }
    return count;
}
int main()
{
    char arr1[20] = "abcdefghjklmn";
    char arr2[] = "hi baby!";
    printf("%d",myStrLen(arr2));
    return 0;
}

错误出现的类型、Debug与Release的区别

  1. C程序常见的错误

    - 编译错误:语法出现问题
    - 链接错误:一般是标识符不存在或拼写错误。
    - 运行时错误:逻辑错误。栈溢出也属于运行时错误
    
  2. Debug和Release的区别

    - Debug被称为调试版本,程序调试找BUG的版本
    - Release被称为发布版本,测试人员测试的就是Release版本
    - Debug版本包含调试信息,不做优化。
    - Release版本不可以调试。但是会做优化,程序大小和运行速度上效果最优。
    

编写方法,调整奇偶数顺序

编写方法,调整奇数偶数顺序,使奇数都位于偶数的前面。也就是说奇数位于前半部分,偶数位于后半部分。

#include <stdio.h>

void print(int arr[], int sz)
{
    int i;
    for (i = 0; i <sz ; ++i) {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
void move(int arr[],int sz)
{
    //起始下标
    int left = 0;
    //结束下标
    int right = sz-1;

    while(left < right)
    {
        //与2取余,如果等于1,则说明是奇数。如果是奇数,则left+1,然后继续循环,知道碰到偶数才停下。
        //注意:当数组中出现全部都是奇数的情况时,这里就可能出现越界的情况,所以加上判断条件:left<right
        while(left<right && arr[left]%2 == 1)
        {
            left++;
        }
        //与2取余,如果等于0,则说明是偶数。如果是偶数,则left-1,然后继续循环,知道碰到奇数才停下。
        //注意:当数组中出现全部都是偶数的情况时,这里就可能出现越界的情况,所以加上判断:right>0。
        //但是如果全是偶数的话,那么left一定是比0大的,所以判断简化为:left<right
        while(left<right && arr[right]%2 == 0)
        {
            right--;
        }
        //如果left在right的左边再交换
        if(left < right)
        {
            int tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
        }
    }
}

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int sz = sizeof(arr)/sizeof(arr[0]);

    print(arr,sz);
    move(arr,sz);
    print(arr,sz);

    return 0;
}

//另外两种思路
//1、把找到的奇数放到首元素位置,其他的顺延。//个人感觉顺延要动整个数组的元素,难以实现,即使实现了,当数组中元素过多时,则效率可能会变低。
//2、再创建一个等长的数组。找到奇数就放从前往后放;找到偶数就从后往前放。//比较麻烦,效率较低。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值