GVKun编程网logo

C语言中String库(1)(c语言string库函数)

11

此处将为大家介绍关于C语言中String库(1)的详细内容,并且为您解答有关c语言string库函数的相关问题,此外,我们还将为您介绍关于C语言下实现的String库、C语言与GLSL语言中struc

此处将为大家介绍关于C语言中String库(1)的详细内容,并且为您解答有关c语言string库函数的相关问题,此外,我们还将为您介绍关于C语言下实现的String库、C语言与GLSL语言中struct的差别、C语言中static的作用及C语言中使用静态函数有何好处、c语言中stract函数的意思的有用信息。

本文目录一览:

C语言中String库(1)(c语言string库函数)

C语言中String库(1)(c语言string库函数)

2008-02-23 14:06
C 语言中,对字符串的处理非常重要,特别在一些网络设备中,处理过程会影响设备的转发和吞吐量。这里详细注释了 String.h 中的所有函数。以下内容摘自《 C 程序设计教程》(美) H.M.Deitel P.J.Deitel 著,薛万鹏等译,机械工业出版社。


     void *memccpy (void *dest,const void *src,int c,size_t n);
    
src 所指向的对象复制 n 个字符到 dest 所指向的对象中。如果复制过程中遇到了字符 c 则停止复制,返回指针指向 dest 中字符 c 的下一个位置;否则返回 NULL

     void *memcpy (void *dest,size_t n);
    
src 所指向的对象复制 n 个字符到 dest 所指向的对象中。返回指针为 dest 的值。

     void *memchr (const void *s,size_t n);
    
s 所指向的对象的前 n 个字符中搜索字符 c 。如果搜索到,返回指针指向字符 c 第一次出现的位置;否则返回 NULL

     int memcmp (const void *s1,const void *s2,size_t n);
    
比较 s1 所指向的对象和 s2 所指向的对象的前 n 个字符。返回值是 s1 s2 第一个不同的字符差值。

     int memicmp (const void *s1,size_t n);
    
比较 s1 所指向的对象和 s2 所指向的对象的前 n 个字符,忽略大小写。返回值是 s1 s2 第一个不同的字符差值。

     void *memmove (void *dest,size_t n);
    
src 所指向的对象复制 n 个字符到 dest 所指向的对象中。返回指针为 dest 的值。不会发生内存重叠。

     void *memset (void *s,size_t n);
    
设置 s 所指向的对象的前 n 个字符为字符 c 。返回指针为 s 的值。

     char *stpcpy (char *dest,const char *src);
    
复制字符串 src dest 中。返回指针为 dest + len(src) 的值。

     char *strcpy (char *dest,const char *src);
    
复制字符串 src dest 中。返回指针为 dest 的值。

     char *strcat (char *dest,const char *src);
    
将字符串 src 添加到 dest 尾部。返回指针为 dest 的值。

     char *strchr (const char *s,int c);
    
在字符串 s 中搜索字符 c 。如果搜索到,返回指针指向字符 c 第一次出现的位置;否则返回 NULL

     int strcmp (const char *s1,const char *s2);
    
比较字符串 s1 和字符串 s2 。返回值是 s1 s2 第一个不同的字符差值。

     int stricmp (const char *s1,const char *s2);
    
比较字符串 s1 和字符串 s2 ,忽略大小写。返回值是 s1 s2 第一个不同的字符差值。

C语言下实现的String库

C语言下实现的String库

C语言的String

C语言作为一门古老的高级语言,对于字符串的支持十分的薄弱。

入门时我们就知道我们使用数组来包含一串的ASCII字符来作为字符串的实现,如

char arr[] = "hello world!";

这样基于长度固定的数组的实现方式就导致了C的字符串的长度是不可变的,但是arr[]的内容却是可变的。

这样的设计导致很多时候我们对字符串的处理十分的麻烦与危险,像我之前写的哈夫曼编码解码的时候,为了盛放解码后的结果,我不得不创建一个非常大的静态数组或者动态分配内存来放置函数产生的长度不定的字符串。

相较于其后辈(如Python/Java,C++基本兼容C的语法,尽管C++实现了自己的string类),C在很多方面也是比较异类的,比如C使用''\0''来标志字符串的结束,因而len(arr)这样的操作的复杂度就达到了O(n),这是一个比较大的开销,而Pascal/Python等的实现都可以做到O(1),同时,由于char类型本身就是最短的整型再加上C语言的弱类型的类型系统,''a''- 32也是完全有效的语法,而在Python中这会引发*TypeError*. 这些问题在C语言诞生的年代不是大问题,毕竟当时没有那么多字符串的处理需求,而且C主要的应用场景也比较偏底层。

而现在,一些选择C实现的程序需要频繁的处理字符串(如 Redis,需要频繁的处理键值对),为了应对这种场景,很多很有意思的自己的String实现都被提了出来。

在这里我主要是介绍ccan的xstring和sds的一些实现的思路。

xstring

/**
 * struct xstring - string metadata
 * @str: pointer to buf
 * @len: current length of buf contents
 * @cap: maximum capacity of buf
 * @truncated: -1 indicates truncation
 */
typedef struct xstring {
    char *str;
    size_t len;
    size_t cap;
    int truncated;
} xstring;

xstring *xstrNew(const size_t size)
{
    char *str;
    xstring *x;

    if (size < 1) {
        errno = EINVAL;
        return NULL;
    }

    str = malloc(size);//mark 1
    if (!str)
        return NULL;

    x = malloc(sizeof(struct xstring));//mark 2
    if (!x) {
        free(str);
        return NULL;
    }

    xstrInit(x, str, size, 0);
    return x;
}

透过xstring结构体与*xstrNew(const size_t size)这个创建新的xstring的函数,ccan的这个实现的思路就比较清晰了,xstring结构体本身占据内存,但是并不存储字符串,字符串在mark 1被分配存储空间,而结构体在mark 2被分配内存。


PS:

在刚刚学习使用C来实现数据结构的时候,我很疑惑为何不能直接

struct xstring* newStruct(){
    struct xstring s;
    return &s;
}

直到后来才逐渐明白了栈上的变量与动态分配的变量的微妙的区别,s在这个函数返回后就已经被销毁了,传出的这个地址是无效的,而对他的引用很可能会导致段错误(segment fault),操作系统,编译原理等课真的会让自己对于程序设计语言获得更深的理解。

而且这种写法当时很有吸引力,毕竟不用malloc,不用强制类型转换。

这种野指针是很多很难修正的错误的来源,有兴趣的同学可以去学习一下Rust语言的所有权系统,很多的概念很有意思。


| xstring | -> | str |

可以看出xstring的实现中内存是分为两个部分的。

Note: xstring只需要编译器支持C89/90。

sds

redis sds(simple dynamic string)是Redis对于str的实现,在这里有官方对于sds实现的一些技巧的介绍,

在这里我会将SDS实现的主要的细节介绍以下。

// sds 类型
typedef char *sds;

// sdshdr 结构
struct sdshdr {

    // buf 已占用长度
    int len;

    // buf 剩余可用长度
    int free;

    // 实际保存字符串数据的地方
    // 利用c99(C99 specification 6.7.2.1.16)中引入的 flexible array member,通过buf来引用sdshdr后面的地址,
    // 详情google "flexible array member"
    char buf[];
};

和上面的实现不太一样的是sds只存储存储的字符串长度以及剩余长度,但是最引人瞩目的无疑是最后的那一个数组声明:

char buf[];

结构体中竟然没有声明数组的大小,这样好像与我们对于数组一贯的印象不符,但是这是合法的特性,叫做柔性数组。

具体的语法细节我不再介绍,但是注意以下几点

  • sizeof(struct sdshdr) == sizeof(len) + sizeof(buf),在x86_64上典型值应该为8个字节(4 + 4),这说明buf[]没有实际占据空间,一个64位系统下的指针就要8个字节。
  • 上面的写法是C99 only的,这个特性应该来自于以下这种写法,

    struct header {
      size_t len;
      unsigned char data[1];
    };

    这种写法下data就是一个 unsigned char*型的指针,可以通过它用来访问存储的字符串。

    //在分配内存的时候,结构体中存储了一个字符,其他的(n-1)个空间在
    //紧随结构体结束地址的地方
    // | struct (char) | (n - 1) char |
    ptr = malloc(sizeof(struct header) + (n-1));

    对比sds中的实现,sds中不存储任何一个数据,只有一个不占据内存空间的标记代表,所有的数据都存储在结构体所占空间后面

    | struct | str |

我们来看这有什么用:

  /*
   * 返回 sds buf 的已占用长度
   */
  static inline size_t sdslen(const sds s) {
      struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
      return sh->len;
  }
  
  /*
   * 返回 sds buf 的可用长度
   */
  static inline size_t sdsavail(const sds s) {
      struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
      return sh->free;
  }
  
  /*
   * 创建一个指定长度的 sds 
   * 如果给定了初始化值 init 的话,那么将 init 复制到 sds 的 buf 当中
   *
   * T = O(N)
   */
  sds sdsnewlen(const void *init, size_t initlen) {
  
      struct sdshdr *sh;
  
      // 有 init ?
      // O(N)
      if (init) {
          sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
      } else {
          sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
      }
  
      // 内存不足,分配失败
      if (sh == NULL) return NULL;
  
      sh->len = initlen;
      sh->free = 0;
  
      // 如果给定了 init 且 initlen 不为 0 的话
      // 那么将 init 的内容复制至 sds buf
      // O(N)
      if (initlen && init)
          memcpy(sh->buf, init, initlen);
  
      // 加上终结符
      sh->buf[initlen] = ''\0'';
  
      // 返回 buf 而不是整个 sdshdr
      return (char*)sh->buf;
  }
  

我们创建一个新的sds的时候,分配sizeof(struct sdshdr) + len + 1大小的空间,len代表不包含结束符号在内的容量,最后我们返回的是字符串开始的地址,这个返回的地址可以直接作为一般的字符串被其他库函数等使用,即Redis所说的二进制兼容的(因为其内部也使用''0''结尾)。

同时结构体的地址可以通过用字符串的地址减去结构体的大小得到

struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));

这样一来sds可以在常数时间内获得字符串的长度。

#include <stdio.h>
#include "./src/simple_dynamic_str.h"

int main() {

    sds s = sdsnew("Hello World! K&R");
    printf("%s\n", s);
    printf("%zu %zu\n", sdslen(s), sdsavail(s));

    printf("%c",s[0]);

    return 0;
}
结果:
Hello World! K&R
16 0
H

这种通过指针的计算获得结构体的地址的方式还是比较少见的技巧,我也只是在Linux内核的task_struct结构体中见识过类似的技巧,当然那个更复杂。

这种操作是很危险的,但是C数组在这方面其实也没有好多少(并没有多出数组越界检查等),不是吗?

在字符串较短时,结构体占据放入空间是比较可观的,更新版本的Redis优化了不同长度的字符串结构体的定义。

/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

总结

这篇文章中有些技巧还是有些难度的,像sds我也是花了一些时间才弄明白其原理,这里的两种实现我个人更偏爱第二种,但是这毕竟是二等公民,没有语言级别的支持是硬伤。

所以如果真的需要大量处理字符串,特别是非纯ASCII码,左转Java/Python etc.

reference:

[redis sds(simple dynamic string)]()

[ccan xstring]()

Redis设计与实现

https://stackoverflow.com/que...

https://redis.io/topics/inter...

C语言与GLSL语言中struct的差别

C语言与GLSL语言中struct的差别



C语言与GLSL语言中struct的差别

对于一个如下struct

structcbChangeEveryFrame

{

     mat4 g_matWorldViewProj;

     mat4 g_matTexture;

     //key crop

     vec4 fCropLBRT;

} test;

C语言中,此处test等价于struct cbChangeEveryFrame ,此处代表结构体类型的别名;

而在GLSL语言中,test是结构体struct cbChangeEveryFrame一个具体的对象,相当于struct cbChangeEveryFrame test  ;

用struct的时候,别忘了定义结束后的分号哦,特别是在下边的情况下,要记得加分号(在GLSL中没有错误提示,可能因为漏掉一个小小的分号,需要查找一会儿,以后注意哦):

struct     Success

{

     mat4 g_matWorldViewProj;

     mat4 g_matTexture;

     //key crop

     vec4 ffCrop;

} ;







C语言中static的作用及C语言中使用静态函数有何好处

C语言中static的作用及C语言中使用静态函数有何好处

想了解Java中static关键字的作用和用法详细介绍,请点击此处了解详情。

在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条,分别是:

一是隐藏功能,对于static修饰的函数和全局变量而言

二是保持持久性功能,对于static修饰的局部变量而言。

三是因为存放在静态区,全局和局部的static修饰的变量,都默认初始化为0

下面我逐一给大家介绍:

(1)先来介绍它的第一条也是最重要的一条:隐藏。

当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c。

下面是a.c的内容

char a = 'A'; // global variable
void msg() 
{
 printf("Hello\n"); 
}

下面是main.c的内容

int main(void)
{ 
 extern char a; // extern variable must be declared before use
 printf("%c ",a);
 (void)msg();
 return 0;
}

程序的运行结果是:

A Hello

你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。

如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。

2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见,但我还是举一个例子。

#include <stdio.h>
int fun(void){
 static int count = 10; // 事实上此赋值语句从来没有执行过
 return count--;
}
int count = 1;
int main(void)
{ 
 printf("global\t\tlocal static\n");
 for(; count <= 10; ++count)
  printf("%d\t\t%d\n",count,fun()); 
 return 0;
}

 程序的运行结果是:

global          local static

1               10

2               9

3               8

4               7

5               6

6               5

7               4

8               3

9               2

10              1

(3)static的第三个作用是默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加'\0'太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是'\0'。不妨做个小实验验证一下。

#include <stdio.h>
int a;
int main(void)
{
 int i;
 static char str[10];
 printf("integer: %d; string: (begin)%s(end)",a,str);
 return 0;
}

程序的运行结果如下

integer: 0; string: (begin)(end)

最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。

以上内容是关于c语言中static的作用。

下面给大家介绍C语言中使用静态函数的好处。

 C语言中使用静态函数的好处:

静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。
关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。 使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。

c语言中static的语义1.static变量:1).局部a.静态局部变量在函数内定义,生存期为整个源程序,但作用域与自动变量相同,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它。b.对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。2).全局全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。但是他们的作用域,非静态全局 变量的作用域是整个源程序(多个源文件可以共同使用); 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。2.static函数(也叫内部函数)只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用。区别于一般的非静态函数(外部函数) static在c里面可以用来修饰变量,也可以用来修饰函数。 先看用来修饰变量的时候。变量在c里面可分为存在全局数据区、栈和堆里。其实我们平时所说的堆栈是栈而不包含对,不要弄混。

c语言中stract函数的意思

c语言中stract函数的意思

c语言中stract函数的意思

1、strcat是用来拼接字符串的,它会将参数 src 字符串复制到参数 dest 所指的字符串尾部。具体用法首先用vs2017新建一个c语言的程序文件,引入头文件,引入strcat函数所在的包“string.h”,最后设置一个主函数:

6af41fa58f86258badc5ac526871f8e.png

2、接着用str函数复制一段话,首先定义一个80位的char类型变量,接着用strcat函数拼接成一句话,str函数接受两个参数,前一个是待拼接的变量,后面的是需要拼接的内容,可以是变量也可以是一串字符;最后用puts函数输出str的内容即可:

c7a1babdc7cf45aebd8a40a407bb959.png

3、按下crtl+F5运行调试程序,在打开的命令提示符中就会显示出完整的一句话了。以上就是strcat函数的用法:

e7a62259bbf067fd52191219997dd5a.png

我们今天的关于C语言中String库(1)c语言string库函数的分享已经告一段落,感谢您的关注,如果您想了解更多关于C语言下实现的String库、C语言与GLSL语言中struct的差别、C语言中static的作用及C语言中使用静态函数有何好处、c语言中stract函数的意思的相关信息,请在本站查询。

本文标签: