通过create_string_buffer、create_unicode_buffer让C语言具备修改字符串的能力

自作多情 提交于 2020-01-28 21:50:02

字符串的修改

我们知道C中不存在字符串这个概念,python中的字符串在C中也是通过字符数组来实现的。我们说在C中创建一个字符数组有两种方式:

char *s1 = "hello world";
char s2[] = "hello world";

这两种方式虽然打印的结果是一样的,并且s1、s2都指向了对应字符数组的首地址,但是内部的结构确是不同的。

  • 1.char *s1 = "hello world";此时这个字符数组是存放在静态存储区里面的,程序编译的时候这块区域就已经确定好了,静态存储区在程序的整个运行期间都是存在的,主要用来存放一些静态变量、全局变量、常量。因此s1只能够访问这个字符数组,却不能够改变它,因为它是一个常量。而char s2[] = "hello world";,这种方式创建的字符数组是存放在栈当中的,可以通过s2这个指针去修改它。
  • 2.char *s1 = "hello world";是在编译的时候就已经确定了,因为是一个常量。而char s2[] = "hello world";则是在运行时才确定。
  • 3.char *s1 = "hello world";创建的字符数组存于静态存储区,char s2[] = "hello world";创建的字符数组存储于栈区,所以s1访问的速度没有s2快。

所以我们说char *s这种方式创建的字符数组在C中是不能修改的,但是我们通过ctypes却可以做到对char *s进行修改:

#include <stdio.h>

int test(char *s1, char s2[6])
{   
    //两种方式都进行修改
    s1[0] = 'a';
    s2[0] = 'a';
    printf("s1 = %s, s2 = %s\n", s1, s2);
}

我们还是将C文件编译成mmp.dll

import ctypes
from ctypes import *

lib = ctypes.CDLL("./mmp.dll")
# 我们看到无论是char *s1,还是char s2[...],我们都可以使用c_char_p这种方式传递
lib.test(c_char_p(b"hello"), c_char_p(b"hello"))  # s1 = aello, s2 = aello

我们看到两种方式都成功修改了,但是即便能修改,我们不建议这么做。不是说不让修改,而是应该换一种方式。如果是需要修改的话,那么不要使用c_char_p的方式来传递,而是建议通过create_string_buffer来给C语言传递可以修改字符的空间。

create_string_buffer

create_string_buffer是ctypes提供的一个函数,表示创建具有一定大小的字符缓存,就理解为字符数组即可。

from ctypes import *

# 传入一个int,表示创建一个具有固定大小的字符缓存,这里是10个
s = create_string_buffer(10)
# 直接打印就是一个对象
print(s)  # <ctypes.c_char_Array_10 object at 0x000001E2E07667C0>
# 也可以调用value方法打印它的值,可以看到什么都没有
print(s.value)  # b''
# 并且它还有一个raw方法,表示C语言中的字符数组,由于长度为10,并且没有内容,所以全部是\x00,就是C语言中的\0
print(s.raw)  # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
# 还可以查看长度
print(len(s))  # 10

当然create_string_buffer如果只传一个int,那么表示创建对应长度的字符缓存。除此之外,还可以指定字节串,此时的字符缓存大小和指定的字节串大小是一致的:

from ctypes import *

# 此时我们直接创建了一个字符缓存
s = create_string_buffer(b"hello")
print(s)  # <ctypes.c_char_Array_6 object at 0x0000021944E467C0>
print(s.value)  # b'hello'
# 我们知道在C中,字符数组是以\0作为结束标记的,所以结尾会有一个\0,因为raw表示C中的字符数组
print(s.raw)  # b'hello\x00'
# 长度为6,b"hello"五个字符再加上\0一共6个
print(len(s))

当然create_string_buffer还可以指定字节串的同时,指定空间大小。

from ctypes import *

# 此时我们直接创建了一个字符缓存,如果不指定容量,那么默认和对应的字符数组大小一致
# 但是我们还可以同时指定容量,记得容量要比前面的字节串的长度要大。
s = create_string_buffer(b"hello", 10)
print(s)  # <ctypes.c_char_Array_10 object at 0x0000019361C067C0>
print(s.value)  # b'hello'
# 长度为10,剩余的5个显然是\0
print(s.raw)  # b'hello\x00\x00\x00\x00\x00'
print(len(s))  # 10

下面我们来看看如何使用create_string_buffer来传递:

#include <stdio.h>

int test(char *s)
{   
    //变量的形式依旧是char *s
    //下面的操作就是相当于把字符数组的索引为5到11的部分换成" satori"
    s[5] = ' ';
    s[6] = 's';
    s[7] = 'a';
    s[8] = 't';
    s[9] = 'o';
    s[10] = 'r';
    s[11] = 'i';
    printf("s = %s\n", s);
}
from ctypes import *

lib = CDLL("./mmp.dll")
s = create_string_buffer(b"hello", 20)
lib.test(s)  # s = hello satori

此时就成功地修改了,我们这里的b"hello"占五个字节,下一个正好是索引为5的地方,然后把索引为5到11的部分换成对应的字符。但是需要注意的是,一定要小心\0,我们知道C语言中一旦遇到了\0就表示这个字符数组结束了。

from ctypes import *

lib = CDLL("./mmp.dll")
# 这里把"hello"换成"hell",看看会发生什么
s = create_string_buffer(b"hell", 20)
lib.test(s)  # s = hell

# 我们看到这里只打印了"hell",这是为什么?
# 我们看一下这个s
print(s.raw)  # b'hell\x00 satori\x00\x00\x00\x00\x00\x00\x00\x00'

# 我们看到这个create_string_buffer返回的对象是可变的,在将s传进去之后被修改了
# 如果没有传递的话,我们知道它是长这样的。
"""
b'hell\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
hell的后面全部是C语言中的\0
修改之后变成了这样
b'hell\x00 satori\x00\x00\x00\x00\x00\x00\x00\x00'

我们看到确实是把索引为5到11(包含11)的部分变成了"satori"
但是我们知道C语言中扫描字符数组的时候一旦遇到了\0,就表示结束了,而hell后面就是\0,
因为即便后面还有内容也不会输出了,所以直接就只打印了hell
"""

另外除了create_string_buffer之外,还有一个create_unicode_buffer,针对于wchar_t,用法和create_string_buffer一样。

C语言中查看字符数组的长度

C语言中如何查看字符数组的长度呢?有两种方法,一种是通过sizeof,一种是通过strlen。话说我说这个干什么?算了,不管了。

#include <stdio.h>
#include <string.h>
int main() {
  char s[] = "hello world";
  //C语言中查看字符串的长度可以使用strlen,这个需要导入string.h头文件。strlen计算的就是字符的个数,不包括\0
  //使用sizeof计算的结果是包含\0的,所以会比strlen计算的结果多1
  printf("%d %d\n", strlen(s), sizeof(s) / sizeof(s[0])); // 11 12
  return 0;
}

但是我们发现字符数组的创建方式是通过char s[]这种形式,如果是char *s呢?

#include <stdio.h>
#include <string.h>
int main() {
  char *s = "hello world";
  printf("%d %d\n", strlen(s), sizeof(s) / sizeof(s[0])); // 11 8
  return 0;
}

我们看到使用strlen计算的结果是一样的,但是使用sizeof得到的结果却是不一样的。因为char *s,这个s我们虽然可以使用它来打印字符数组,但它本质上是一个指针,一个指针在64位机器上占8个字节,所以结果是8。而char s[]中的s虽然也指向字符数组的首地址,但它本质上是一个数组名,我们使用sizeof查看得到的结果还是字符数组中所有元素的总大小。

艾玛,你扯到C上面干啥。。。。。。你又不会C。。。。。。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!