牛骨文教育服务平台(让学习变的简单)
博文笔记

03字符串操作和一级指针内存模型

创建时间:2017-03-12 投稿人: 浏览次数:892

  • 字符串操作和一级指针内存模型
    • 字符串的基本操作
      • 1 关于字符串不得不说的话
      • 2 字符数组初始化
      • 3 用字符串初始化字符数组
      • 4 通过数组下标和指针访问字符串内容
    • 一级指针内存模型
    • 字符串copy函数推演过程
    • strstr模型
    • 两头堵模型
    • 字符串反转模型
    • 两个小练习
    • 一级指针易错模型分析
    • 关于const

1. 字符串的基本操作

1.1 关于字符串不得不说的话

  • C语言的字符串是以0或者说’’结尾的字符串
  • C语言实际上根本没有字符串这个数据类型,全靠编译器来解释源代码里面的字符串,原理是用字符数组模拟字符串,只不过字符数组的最后一个元素是0(’’)
  • 字符串的内存分配可在堆上、栈上、全局区、实现内存分配,通常字符串字面常量就是在全局数据区分配的内存

1.2 字符数组初始化

  1. 指定长度初始化

    • 当初始值的个数长于指定的数组长度时,编译器报错
      buf3[2] = {"a", "b", "c", "d"};
    • 当初始值的个数小于指定的数组长度时,后面的数组元素默认初始化为0
      char buf2[100] = {"a", "b", "c", "d"};

    后面的buf2[4]-buf2[99]自动初始化为 0,此时内存在栈上分配

  2. 不指定长度初始化
    C编译器会自动帮程序员 求元素的个数
    char buf1[] = {"a", "b", "c", "d"}; //buf1是一个数组 不是一个以0结尾的字符串

1.3 用字符串初始化字符数组

  • 编译器自动将字符串字面常量逐一赋值给数组元素,并将最后一个元素赋值为0
  • 涉及到计算字符串长度的函数是strlen
  • 计算数组长度的是操作符sizeof,数组和指针都是特定的数据类型
char buf3[] = "abcd"; /* buf3 作为字符数组 应该是5个字节 ,作为字符串 应该4个字节*/

1.4 通过数组下标和指针访问字符串内容

  • 数组名是数组首元素的地址,是一个常量指针,不可修改;

因为编译器要保证局部变量内存首地址不变,这样才可以在函数返回时安全释放内存,否则一旦编译器允许修改数组名的值,则释放内存的时候有可能释放掉别的地址开头的内存或者浪费一部分内存

  • 当sizeof作用于数组名的时候,返回的是整个数组的大小
  • 当取地址符(&)作用于数组名的时候,返回的是整个数组的地址,返回的数据类型对应于一整块数组的内存
  • []的本质 :和*p 是一样,只不过是符合程序员的阅读习惯,不过写成[]的效率稍微低一点,因为编译器多一条指令将其转换为指针与下标相加的操作,同时存在
    p[i] = *(p+i)

buf5[i] ===> buf5[0+i]; ==> *(buf5+i);

2. 一级指针内存模型

示例代码

void main()
{
    char buf[20]= "aaaa";
    char buf2[] = "bbbb";
    char *p1 = "111111";
    char *p2 = malloc(100); strcpy(p2, "3333");

    system("pause");
    return ;
}

对应的内存模型

注意:只要是字符串字面常量,都必须先要在全局静态区存储起来,然后再拷贝到栈区或者堆区

3. 字符串copy函数推演过程

  • 最次的字符串拷贝代码
#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char *src = "abcd";

    char buf[64];

    int i = 0;
    for (i = 0; *(src + i) != ""; i++)
    {
        buf[i] = *(src + i);
    }
    buf[i] = "";

    printf("buf:%s
", buf);

    system("PAUSE");
    return 1;
}
  • 第一种风格的拷贝函数
#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void str_cpy01(char *from,char *to)
{
    int i = 0;
    for (i = 0; *from  != ""; from++,to++)
    {
        *to = *from;
    }
    *to = "";
}

int main(void)
{
    char *src = "abcd";

    char buf[64];


    str_cpy01(src,buf);
    printf("buf:%s
", buf);

    system("PAUSE");
    return 1;
}
  • 第二种风格的copy函数
#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//*操作 和++的操作
//++ 优先级高 
void str_cpy02(char *from,char *to)
{
    int i = 0;
    for (i = 0; *from  != "";)
    {
        *to++ = *from++;
    }
    *to = "";
}

int main(void)
{
    char *src = "abcd";

    char buf[64];


    str_cpy02(src, buf);
    printf("buf:%s
", buf);

    system("PAUSE");
    return 1;
}
  • 第三种风格
#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void str_cpy03(char *from,char *to)
{
    while ((*to = *from) != ""){
        from++;
        to++;
    }
}

int main(void)
{
    char *src = "abcd";

    char buf[64];


    str_cpy03(src, buf);
    printf("buf:%s
", buf);

    system("PAUSE");
    return 1;
}
  • 第四种风格
#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void str_cpy04(char *from,char *to)
{
    while ((*to++ = *from++) != "");
}

int main(void)
{
    char *src = "abcd";

    char buf[64];


    str_cpy04(src, buf);
    printf("buf:%s
", buf);

    system("PAUSE");
    return 1;
}
  • 第五种风格
#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void str_cpy05(char *from,char *to)
{
    while ((*to++ = *from++));
}

int main(void)
{
    char *src = "abcd";

    char buf[64];


    str_cpy05(src, buf);
    printf("buf:%s
", buf);

    system("PAUSE");
    return 1;
}
  • 第六种风格:避免操作空指针以及引入辅助指针变量增强程序的健壮性
#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int str_cpy06(char *from, char *to)
{
    char * tmpfrom = from;//避免修改形参

    char * tmpto = to;

    if (from == NULL || to == NULL)//避免操作空指针
        return -1;

    while (*tmpto++ = *tmpfrom++);

    printf("from:%s
",from);
    return 0;
}

int main(void)
{
    char *src = "abcd";

    char buf[64];


    str_cpy06(src, buf);
    printf("buf:%s
", buf);

    system("PAUSE");
    return 1;
}

4. strstr模型

  • strstr–do-while模型
#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int getCountDoWhile(char * p_str, char *sub_str, int *ncount)
{
    int ret = 0;

    char * src = p_str;
    int tmp_count = 0;

    if (p_str == NULL || sub_str == NULL || ncount == NULL)//合法性检测
    {
        ret = -1;
        printf("%s is error:(p_str == NULL || sub_str == NULL || ncount == NULL)
", __FUNCTION__);
        return  ret;
    }

    do{
        src = strstr(src, sub_str);
        if (src != NULL)
        {
            tmp_count++;
            src += strlen(sub_str);
        }
        else{
            break;
        }


    } while (*src != "");

    *ncount = tmp_count;

    return ret;
}

int main(void)
{
    //strstr(str, str2)
    int ncount = 0;
    int ret = 0;

    char *p = "11abcd111122abcd3333322abcd3333322qqq";
    ret = getCountDoWhile(p,"abcd",&ncount);

    if (ret != 0){
        printf("func getCountDoWhile() err:%d 
", ret);
        return ret;
    }

    printf("ncount:%d 
", ncount);
    system("PAUSE");
    return ret;
}
  • strstr–while模型
#define _CRT_SECURE_NO_WARNINGS


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int getCountWhile(char * p_str, char *sub_str, int *ncount)
{
    int ret = 0;

    char * src = p_str;
    int tmp_count = 0;

    if (p_str == NULL || sub_str == NULL || ncount == NULL)//合法性检测
    {
        ret = -1;
        printf("%s is error:(p_str == NULL || sub_str == NULL || ncount == NULL)
", __FUNCTION__);
        return  ret;
    }
    while (src = strstr(src,sub_str)){
        tmp_count++;
        src += strlen(sub_str);
        if (*src == "")
            break;
    }
    *ncount = tmp_count;
    return ret;
}

int main(void)
{
    //strstr(str, str2)
    int ncount = 0;
    int ret = 0;

    char *p = "11abcd111122abcd3333322abcd3333322qqq";
    ret = getCountWhile(p, "abcd", &ncount);

    if (ret != 0){
        printf("func getCountWhile() err:%d 
", ret);
        return ret;
    }

    printf("ncount:%d 
", ncount);
    system("PAUSE");
    return ret;
}

5. 两头堵模型

  • 求出字符串(首尾有空格)有效字符的长度
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<ctype.h>
int getCount(char * src,int * count)
{
    int ret = 0;

    char *tmp_p = src;
    int tmp_count = 0;

    int head = 0,tail = 0;
    if(src == NULL || count == NULL)
    {
        ret = -1;
        printf("%s is error!(src == NULL || count == NULL)
",__FUNCTION__);
        return ret;
    }

    while(*(tmp_p+head) != "" && isspace(tmp_p[head])){
        head++;
    }
    tail = strlen(src)-1;

    while(*(tmp_p+tail) != "" && isspace(tmp_p[tail])){
        tail--;
    }

    tmp_count = tail - head + 1;

    *count = tmp_count;


    return ret;
}

int main(void)
{
    char *p = "    abcdefg    ";
    int ret = 0;

    int ncount = 0;

    ret = getCount(p,&ncount);
    if(ret != 0){
        printf("getCount failed!ret : %d
",ret);
        return ret;
    }
    printf("ncount:%d
",ncount);

    printf("Hello World!
");
    return 0;
}
  • 去除字符串中的空格,输出新字符串

注意:向一个指针指向的内存空间拷贝数据的时候要留心是否可以向该片内存空间写入数据,常量数据区是不可以写入数据的。

int trimeSpace(char *src,char *new_str)
{
    int ret = 0;

    char *tmp_p = src;

    int head = 0,tail=0;

    if(src == NULL || new_str == NULL)
    {
        ret = -1;
        printf("%s is error!(src == NULL || count == NULL)
",__FUNCTION__);
        return ret;
    }

    tail = strlen(src)-1;

    while(tmp_p[head] != "" && isspace(tmp_p[head])){
        head++;
    }

    while(tmp_p[tail] != "" && isspace(tmp_p[tail])){
        tail--;
    }

    strncpy(new_str,tmp_p+head,tail-head+1);
    new_str[tail-head+1] = "";

    return ret;
}

int main(void)
{
    char *p = "    abcdefg    ";
    char buf[1024];
    int ret = 0;



    ret = trimeSpace(p,buf);
    if(ret != 0){
        printf("getCount failed!ret : %d
",ret);
        return ret;
    }
    printf("buf:%s
",buf);

    printf("Hello World!
");
    return 0;
}

注意:strncpy函数并不会在目标字符串后面自动添加’’

6. 字符串反转模型

  • 类似两头堵模型的字符串反转
int inverse(char *src_str)
{
    int ret = 0;
    int len = strlen(src_str);
    char * head = src_str;
    char * tail = src_str + (len-1);

    char tmp_c = 0;

    if(src_str == NULL){
        ret = -1;
        printf("%s is error!src_str == NULL
",__FUNCTION__);
        return ret;
    }

    while(head < tail){
        tmp_c = *head;
        *head++ = *tail;
        *tail-- = tmp_c;
    }

    return ret;
}

int main(void)
{
    //char *p = "    abcdefg    ";//不能用字符串常量赋值给一个指针进行该实验,因为常量区不可修改数据
    char buf[1024]="abcdefg";
    int ret = 0;

    ret = inverse(buf);
    if(ret != 0){
        printf("inverse failed!ret : %d
",ret);
        return ret;
    }
    printf("buf:%s
",buf);

    printf("Hello World!
");
    return 0;
}
  • 利用堆栈的字符串反转(递归)

递归简单的逆序打印不存储结果

int inverse_02(char *src_str)
{
    int ret = 0;

    char *tmp_p = src_str;

    if(src_str == NULL){//合法性检测
        ret = -1;
        printf("%s is error!src_str == NULL
",__FUNCTION__);
        return ret;
    }

    if(*tmp_p == "")//递归停止的正常条件
    {
        return 0;
    }

    inverse_02(tmp_p+1);//将tmp_p指向的内存空间的数据的地址逐个压栈

    printf("%c",*tmp_p);

    return ret;
}

int main(void)
{

    char buf[1024]="abcdefg";
    int ret = 0;

    printf("buf:");
    ret = inverse_02(buf);
    if(ret != 0){
        printf("inverse_02 failed!ret : %d
",ret);
        return ret;
    }

    printf("
Hello World!
");
    return 0;
}

递归结合全局变量

char g_buf[1024]={0};

int inverse_03(char *src_str)
{
    int ret = 0;

    char *tmp_p = src_str;

    if(src_str == NULL){//合法性检测
        ret = -1;
        printf("%s is error!src_str == NULL
",__FUNCTION__);
        return ret;
    }

    if(*tmp_p == "")//递归停止的正常条件
    {
        return 0;
    }

    inverse_03(tmp_p+1);//将tmp_p指向的内存空间的数据的地址逐个压栈

    strncat(g_buf,tmp_p,1);

    return ret;
}

int main(void)
{

    char buf[1024]="abcdefg";
    int ret = 0;


    ret = inverse_03(buf);
    if(ret != 0){
        printf("inverse_03 failed!ret : %d
",ret);
        return ret;
    }
    printf("g_buf:%s
",g_buf);
    printf("
Hello World!
");
    return 0;
}

递归结合局部变量(指针做函数参数)

int inverse_04(char *src_str,char *str_out)
{
    int ret = 0;

    char *tmp_p = src_str;

    if(src_str == NULL || str_out == NULL){//合法性检测
        ret = -1;
        printf("%s is error!(src_str == NULL || str_out == NULL)
",__FUNCTION__);
        return ret;
    }

    if(*tmp_p == "")//递归停止的正常条件
    {
        return 0;
    }

    inverse_04(tmp_p+1,str_out);//将tmp_p指向的内存空间的数据的地址逐个压栈

    strncat(str_out,tmp_p,1);

    return ret;
}

int main(void)
{

    char buf[1024]="abcdefg";
    char res[1024];

    int ret = 0;

    memset(res,0,sizeof(res));


    ret = inverse_04(buf,res);
    if(ret != 0){
        printf("inverse_04 failed!ret : %d
",ret);
        return ret;
    }
    printf("res:%s
",res);
    printf("
Hello World!
");
    return 0;
}

7. 两个小练习

  • 有一个字符串”1a2b3d4z”,;
    要求写一个函数实现如下功能,
    功能1:把偶数位字符挑选出来,组成一个字符串1。valude;20分
    功能2:把奇数位字符挑选出来,组成一个字符串2,valude 20
    功能3:把字符串1和字符串2,通过函数参数,传送给main,并打印。
    功能4:主函数能测试通过。
    int getStr1Str2(char *souce, char *buf1, char *buf2);
int getStr1Str2(char *souce, char *buf1, char *buf2)
{
    int ret = 0;
    char *src = souce;
    if(souce == NULL || buf1 == NULL || buf2 ==NULL){
        ret = -1;
        printf("%s is error!(source == NULL || buf1 == NULL || buf2 ==NULL)
",__FUNCTION__);
        return ret;
    }

    while(*src != ""){

        *buf1 = *src;
        src++;
        buf1++;

        *buf2 = *src;
        src++;
        buf2++;
    }


    return ret;
}
int main(void)
{

    char src[1024]="1a2b3c4d";
    char buf1[512]={0};
    char buf2[512]={0};

    int ret = 0;
    ret = getStr1Str2(src,buf1,buf2);
    if(ret != 0){
        printf("getStr1Str2 failed!ret : %d
",ret);
        return ret;
    }
    printf("src:%s
",src);
    printf("偶数位的数据buf2:%s
",buf2);
    printf("奇数位的数据buf1:%s
",buf1);
    printf("
Hello World!
");
    return 0;
}
  • 键值对(”key = valude”)字符串,在开发中经常使用;
    要求1:请自己定义一个接口,实现根据key获取valude;40分
    要求2:编写测试用例。30分
    要求3:键值对中间可能有n多空格,请去除空格30分
    注意:键值对字符串格式可能如下:
    “key1 = valude1”
    “key2 = valude2”
    “key3 = valude3”
    “key4 = valude4”
    “key5 = “
    “key6 =“
    “key7 = “

int getKeyByValude(char *keyvaluebuf, char *keybuf, char *valuebuf);

int trimeSpace(char *src,char *new_str)
{
    int ret = 0;

    char *tmp_p = src;

    int head = 0,tail=0;

    if(src == NULL || new_str == NULL)
    {
        ret = -1;
        printf("%s is error!(src == NULL || count == NULL)
",__FUNCTION__);
        return ret;
    }

    tail = strlen(src)-1;

    while(tmp_p[head] != "" && isspace(tmp_p[head])){
        head++;
    }

    while(tmp_p[tail] != "" && isspace(tmp_p[tail])){
        tail--;
    }

    strncpy(new_str,tmp_p+head,tail-head+1);
    new_str[tail-head+1] = "";

    return ret;
}

int getKeyByValude(char *keyvaluebuf,  char *keybuf,  char *valuebuf)
{
    int ret = 0;

    char * p = keyvaluebuf;//辅助指针变量初始化

    if(keyvaluebuf == NULL || keybuf == NULL || valuebuf ==NULL){
        ret = -1;
         printf("%s is error!(keyvaluebuf == NULL || keybuf == NULL || valuebuf ==NULL)
",__FUNCTION__);
        return ret;
    }
    //1. 从母串中查找子串
    p = strstr(p,keybuf);
    if(p == NULL){
        ret = -1;
        return ret;
    }

    p += strlen(keybuf);//辅助指针变量再次初始化使得下一次可用


    //2. 判断有没有等号
    p = strstr(p,"=");
    if(p == NULL){
        ret = -1;
        return ret;
    }

    p += strlen("=");//辅助指针变量再次初始化使得下一次可用,指向等号后面开始的地方

    //3. 去除等号后面的空格
    ret = trimeSpace(p,valuebuf);//调用自己封装的函数
    if(ret != 0){
        printf("getKeyByValude failed 
");
        return ret;
    }

    return ret;

}

int main(void)
{

   char *keyandvalue = "key2 =         value2   ";
   char *key = "key2";

   char res_str[1024]={0};

    int ret = 0;
    ret = getKeyByValude(keyandvalue,key,res_str);
    if(ret != 0){
        printf("getKeyByValude failed!ret : %d
",ret);
        return ret;
    }
    printf("res_str:%s
",res_str);

    printf("
Hello World!
");
    return 0;
}

8. 一级指针易错模型分析

  • 字符串作函数参数的时候,记得在被调函数进行合法性检测
  • 对指针变量进行合法性检测主要是检测指针本身的值而不是检测指针变量指向的内存空间的值
    比如下面代码就有错误:
void copy_str21(char *from, char *to)
{

    if (*NULL = "" || *to!=’’) //错误判断
    {
        Printf(“func copy_str21() err
”);
        return; 
    }


    for (; *from!=""; from++, to++)
    {
        *to = *from;
    }
    *to = "";
}
  • 字符串初始化字符数组因为忽略C风格的字符串默认结尾有一个’’作为结尾符号而发生越界
    如:
    char buf[3] = "abc";

  • 不断修改指针变量的值会导致指针指向的内存区域发生变化,从而在涉及到free的时候,释放错误的内存块导致程序崩溃
    如:

  • 试图将被调函数里分配的内存空间向外传递,但如果被调函数实在栈空间分配的内存则传递是不可能成功的,这点需要格外注意

  • 从主调函数传递一个实参的地址给被调函数,被调函数制图修改该实参的值,需要注意先使用间接访问操作符取出相应地址处的内存数据,再进行修改操作,这里主要是由于*和++操作符的优先级不一样导致的。
    *mycount++;*(mycount)++;

9. 关于const

  • const含义:const是定义常量==》const意味着只读
  • 示例代码
int main()
{
const int a; 
int const b; 

const char *c;
char * const d; char buf[100]
const char * const  e ;
return 0;
}
  • 第一个第二个意思一样 代表一个常整形数
  • 第三个 c是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改)
  • 第四个 d 常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)
  • 第五个 e一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)

  • 合理的利用const可以带来的好处:

    1. 指针做函数参数,可以有效的提高代码可读性,减少bug;
    2. 清楚的分清参数的输入和输出特性
  • 结论:

    1. 指针变量和它所指向的内存空间变量,是两个不同的概念。。。。。。
    2. 看const 是放在*的左边还是右边 放在右边修饰指针变量,放在左边修饰所指向的内存变量
    3. C语言中的const是个冒牌货,通过指针可以绕过他的只读属性
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。