C字符串传递

传递字符串很简单,在函数调用中,用一个计算结果是char类型变量地址的表达式即可。在参数列表中,把参数声明为char指针。有趣的事情发生在函数内部使用字符串时。

取决于不同的字符串声明方式,有几种方法可以把字符串的地址传递给函数。在本节中,我们会利用一个模拟strlen的函数说明这些技术,该函数的实现如下代码所示。我们用括号来强制后面的自增操作符先执行,使得指针加1。否则加1的就是string引用的字符了,这不是我们想要的结果。

size_t stringLength(char* string) {
    size_t length = 0;
    while(*(string++)) {
        length++;
    }
    return length;
}

注意 字符串实际上应该以char常量的指针的形式传递。

让我们从下面的声明开始:

char simpleArray[] = "simple string";
char *simplePtr = (char*)malloc(strlen("simple string")+1);
strcpy(simplePtr, "simple string");

要对这个指针调用此函数,只要用指针名字即可:

printf("%d\n",stringLength(simplePtr));

要使用数组调用函数,我们有三种选择,如下所示。在第一个语句中,我们用了数组的名字,这会返回其地址。在第二个语句中,显式使用了取地址操作符,不过这样写有冗余,没有必要,而且会产生警告。在第三个语句中,我们对数组第一个元素用了取地址操作符,这样可以工作,不过有点繁琐:

printf("%d\n",stringLength(simpleArray));
printf("%d\n",stringLength(&simpleArray));
printf("%d\n",stringLength(&simpleArray[0]));

图5-12说明了stringLength函数的内存分配情况。

enter image description here

图5-12:传递字符串

现在让我们把注意力转移到形参的声明方式上。在前面stringLength的实现中,我们把参数声明为char指针,不过也可以像下面这样用数组表示法:

size_t stringLength(char string[]) { ... }

函数体还是一样,这个变化不会对函数的调用方式及其行为造成影响。

传递字符常量的指针

以字符常量指针的形式传递字符串指针是很常见也很有用的技术,这样可以用指针传递字符串,同时也能防止传递的字符串被修改。下面对stringLength函数更好的实现就是利用了这种声明:

size_t stringLength(const char* string) {
    size_t length = 0;
    while(*(string++)) {
        length++;
    }
    return length;
}

如果我们试图像下面这样修改原字符串,那么就会产生一个编译时错误消息:

size_t stringLength(const char* string) {
        ...
    *string = 'A';
        ...
}

传递需要初始化的字符串

有些情况下我们想让函数返回一个由该函数初始化的字符串。假设我们想传递一个部件的信息,比如名字和数量,然后让函数返回表示这个信息的格式化字符串。通过把格式化处理放在函数内部,我们可以在程序的不同部分重用这个函数。

不过,我们得决定是给函数传递一个空缓冲区让它填充并返回,还是让函数动态分配缓冲区并返回。

要传递缓冲区:

  • 必须传递缓冲区的地址和长度;
  • 调用者负责释放缓冲区;
  • 函数通常返回缓冲区的指针。

这种方法把分配和释放缓冲区的责任都交给了调用者。虽然没有必要,返回缓冲区指针很常见,strcpy或类似函数就是这种情况。下面的format函数说明了这种方法:

char* format(char *buffer, size_t size,
        const char* name, size_t quantity, size_t weight) {
    snprintf(buffer, size, "Item: %s Quantity: %u Weight: %u",
            name, quantity, weight);
    return buffer;
}

这里用了snprintf函数来简化字符串格式化,该函数写入第一个参数指向的缓冲区。第二个参数指定缓冲区的长度,函数不会越过缓冲区写入。其他方面,这个函数和printf函数的行为一样。

下面的语句说明了这个函数的用法。它假设缓冲区已被声明为一个数组。如果已动态分配缓冲区内存,则需要传入分配的内存大小,而不是使用函数的大小。

printf("%s\n",format(buffer,sizeof(buffer),"Axle",25,45));

输出如下:

Item: Axle Quantity: 25 Weight: 45

通过返回缓冲区的指针,我们可以将函数作为printf函数的参数。

还有一种方法是传递NULL作为缓冲区地址,这表示调用者不想提供缓冲区,或者它不确定缓冲区应该是多大。这样的函数实现列在了下面,在计算长度时,10 + 10子表达式表示数量和重量可能的最大宽度,而1则是为NUL终结符留下空间:

char* format(char *buffer, size_t size,
        const char* name, size_t quantity, size_t weight) {

    char *formatString = "Item: %s Quantity: %u Weight: %u";
    size_t formatStringLength = strlen(formatString)-6;
       size_t nameLength = strlen(name);
    size_t length = formatStringLength + nameLength +
        10 + 10 + 1;

    if(buffer == NULL) {
        buffer = (char*)malloc(length);
        size = length;
    }
    snprintf(buffer, size, formatString, name, quantity, weight);
    return buffer;
}

函数使用的变量取决于应用程序的需要。第二种方法的主要缺点在于调用者现在要负责释放分配的内存,调用者需要对函数的使用方法了如指掌,否则可能很容易产生内存泄漏。

给应用程序传递参数

main函数通常是应用程序第一个执行的函数。对基于命令行的程序来说,通过为其传递信息来打开某种行为的开关或控制某种行为很常见。可以用这些参数来指定要处理的文件或是配置应用程序的输出。比如说,Linux的ls命令会基于接收到的参数列出当前目录下的文件。

C用传统的argcargv参数支持命令行参数。第一个参数argc,是一个指定传递的参数数量的整数。系统至少会传递一个参数,这个参数是可执行文件的名字。第二个参数argv,通常被看做字符串指针的一维数组,每个指针引用一个命令行参数。

下面的main函数只是简单地列出了它的参数,每行一个。在这个版本中,argv被声明为一个char指针的指针。

int main(int argc, char** argv) {
    for(int i=0; i<argc; i++) {
        printf("argv[%d] %s\n",i,argv[i]);
    }
    ...
}

程序可以用下面的命令执行:

process.exe -f names.txt limit=12 -verbose

输出如下:

argv[0] c:/process.exe
argv[1] -f
argv[2] names.txt
argv[3] limit=12
argv[4] -verbose

使用空格将每个命令行参数分开,这个程序的内存分配如图5-13所示。

enter image description here

图5-13:使用argc/argv

argv的声明可以简化如下:

int main(int argc, char* argv[]) {

这跟char** argv是等价的,指针多层引用详细解释了这种表示法。

赞(1)

评论 抢沙发

评论前必须登录!

 

C指针