原文:深入理解C指针之五:指针和字符串

  基础概念

  字符串可以分配到内存的不同区域,通常使用指针来支持字符串操作。字符串是以ASCII字符NUL结尾的字符序列。ASCII字符NUL表示为\0。字符串通常存储在数组或者从堆上分配的内存中。不过,并非所有的字符数组都是字符串。例如,字符数组可能没有NUL字符。

  C中有两种类型的字符串。

  * 单字节字符串。由char数据类型组成的序列。

  * 宽字符串。由wchar_t数据类型组成的序列。

  wchar_t数据类型用来表示宽字符串,可能是16位或32位宽。这两种字符串都以NUL结尾。宽字符主要用来支持非拉丁字符集,对于支持外语的应用程序很有用。

  字符串的长度是除了NUL字符之外的字符数。为字符串分配内存时,要记得为NUL字符预留空间。注意NUL是一个字符,定义为\0,与NULL(void*(0))不一样。NULL用来表示一种特殊的指针。

  字符常量是单引号引起来的字符序列。字符常量通常由一个字符组成,也可以包含多个字符,如转义字符。在C中,它们的类型是int。char的长度是1字节,而一个字符字面量的长度是 sizeof(int) 个字节。

printf("%d\n", sizeof('a'));//4

  声明字符串的方式有3种:字面量字符数组字符指针。字符串字面量是用双引号引起来的字符序列,常用来进行初始化,它们位于字符串字面量池中。如果声明一个拥有32个字符的数组,那么只能放31个字符串文本,因为字符串要以NUL结尾。字符串在内存中的位置取决于声明的位置。

  定义字面量时通常会将其分配在字面量池中,多次用到同一个字面量时,池中通常只有一份副本。通常认为字面量是不可变的。你可以关闭字面量池来生成多个副本。在哪里使用字符串字面量不重要,它没有作用域的概念。

  有些编译器允许修改字符串,因此把不希望被修改的字符串声明为常量是个不错的选择。

  字符串初始化方式取决于变量被声明为数组还是指针,字符串所用的内存要么是数组要么是指针指向的一块内存。我们可以从字符串字面量或其它地方(比如标准输入)得到字符。

char head[] = "hello man";
printf("size of head is : %d\n",sizeof(head));
//size of head is : 10

  可以看到 "hello man"有9个字符,但是长度是10,因为有NUL字符。还可以strcpy函数初始化数组。

char head1[10];
strcpy(head1,"hello man");

  注意不要用数组的名字作为左值。

  动态内存分配可以提供更多灵活性,也可能让内存存在更久。通常使用malloc和strcpy来初始化字符串。

char* head2 = (char*) malloc (strlen("hello man")+1);
strcpy(head2,"hello man");

  注意在使用malloc决定所需内存长度时,要为NUL预留空间,并且使用strlen而不是sizeof。sizeof会返回数组和指针的长度,而不是字符串的长度。如果用字符串字面量来初始化指针会导致指针指向字符串字面量池。注意不要把字符字面量赋给指针,因为它是int类型。可以把字符字面量赋给解引后的指针。

*(head2 + 7) = 'e';
printf("head2 is %s\n", head2);
//head2 is hello men

  总之字符串可能位于全局或静态内存(global or static array),也可能位于字符串字面量池({……}),可能位于堆上(malloc),可能位于函数的栈帧里(char array[])。字符串的位置决定它能存在多久以及哪些程序可以访问它。全局内存的字符串会一直存在,可以被多个函数访问;静态字符串也一直存在,但只有定义它的函数能访问;堆上的字符串可以被多个函数访问,直到被释放前都存在。

  标准字符串操作

  比较字符串的标准方法是strcmp函数。其原型如下。

int strcmp(const char* s1, const char* s2);

  如果两个字符串相等,返回0。s1大于s2,返回正数。s1小于s2,返回负数。

char command[16];
printf("enter a command :");
scanf("%s",command);
if(strcmp(command,"quit")==0)
{
printf("you typed quit!\n");
}
else
{
printf("i don't know what you typed!\n");
}

  注意如果此处使用if(command == "quit")的话,被比较的实际是command的地址和字符串字面量的地址。

  复制字符串通常使用strcpy函数实现,其原型如下:

char* strcpy(char* s1, const char* s2);

  有一类应用程序会读入一系列字符串,挨个存入占最少内存的数组。先创建一个足够长的字符串数组,其长度足以容纳用户允许输入的最长字符串,然后把字符串读入这个数组。有了读取的字符串,我们就能根据字符串的长度分配合适的内存。

char mynames[32];
char* myname[30];
size_t count = 0; printf("enter a name please:");
scanf("%s",mynames);
myname[count] = (char*) malloc (strlen(mynames)+1);
strcpy(myname[count], mynames);
count++;

   可以在一个循环里重复这个操作。

  两个指针可以引用同一个字符串。两个指针引用同一个地址称为别名。把一个指针赋值给另一个指针只是复制了字符串的地址而已。

  字符串拼接涉及两个字符串的合并。通常用strcat来执行这种操作。这个函数的原型为:

char* strcat(char* s1, const char* s2);

  下面是如何使用缓冲区拼接字符串。

char* error = "ERROR: ";
char* errormsg = "not enough memory!";
char* _buffer = (char*) malloc (strlen(error) + strlen(errormsg) +1);
strcpy(_buffer, error);
strcpy(_buffer, errormsg);
printf("%s\n", _buffer);
printf("%s\n", error);
printf("%s\n", errormsg);

  如果直接使用strcpy(error, errormsg)的话,可能会覆盖error字符串字面量地址后面某些未知的内容,因为我们没有为新的字符串分配独立内存。拼接字符串常见的错误就是没有为新字符串额外分配空间。此外注意不要使用字符字面量代替字符串字面量作为该函数的参数。

  传递和返回字符串   

   先定义一个函数。

size_t strLength(char* string){
size_t length = 0;
while(*(string++)){
length++;
}
return length;
} char simpleArray[] = "simple string";
char* simplePtr = (char*) malloc (strlen("simple string")+1);
strcpy(simplePtr, "simple string"); printf("%d\n", strLength(simplePtr));//13

  对指针调用这个函数,只需要传入指针的名字。对数组使用该函数也可以这么做。在这里数组的名字被解释成了地址。

printf("%d\n", strLength(simpleArray));

  你也可以对数组的0下标使用取地址操作符,但是那样太繁琐了:strLength(&simpleArray[0])。

  把参数声明为指向字符常量的指针,可以防止字符串被修改。假如要让函数返回一个由该函数初始化的字符串,必须决定是否由函数调用者负责释放分配的内存。如果在函数内部动态分配内存并返回指向该内存的指针,那么调用者必须负责最终释放该内存,这要求调用者必须清楚函数的使用方法。

  main函数通常是应用程序第一个执行的函数。对基于命令行的程序来说,通过为其传递某些信息来打开某些功能的开关很常见。比如linux下的ls命令会通过接受 -la 等参数来执行不同行为。C通过argcargv参数支持命令行参数。第一个参数argc是一个整数,用来指定传递参数的数量。系统至少会传递一个参数,这个参数是可执行文件的名字。第二个参数argv,通常被看做字符串指针的一维数组,每个指针引用一个命令行参数。

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

  可以看到,不附加任何参数,默认自带的一个参数就是 ./mysender,是我的编译后文件的名字。试试用以下方式执行:./mysender -f jack -la limit = 100,此时输出为:

argv[0]is ./mysender
argv[1]is -f
argv[2]is jack
argv[3]is -la
argv[4]is limit
argv[5]is =
argv[6]is 100

  由于我把 "=" 符号用空格分开了,结果 "=" 被当做一个参数了。实际上应该用空格把每个参数分开,参数本身不应包含空格了。

  函数返回字符串时,返回的实际是字符串的地址。这可能是字符串字面量的地址,可能是动态内存的地址,可能是本地字符串变量的地址。

  先看第一种情况。对于静态的指向字符串字面量的指针,应该注意在不同地方重复使用会覆盖上一次的结果。字符串并非总是被看做常量,可以通过命令关闭字符串常量池,把字符串声明为常量可以防止字符串被修改。如果返回的是动态分配的内存,那么一定要注意防止内存泄露。返回局部变量字符串的地址可能有问题,因为函数执行完毕后该处内存可能被别的栈帧覆写。比如你在函数里声明一个字符串数组并初始化了,然后返回数组的地址,这个数组所占用的内存是不安全的,面临被其它函数的栈帧覆写的风险。

  函数指针和字符串  

   通过函数指针控制程序执行是一种非常灵活的方法。

#include <stdio.h>
#include <stdlib.h>
#include <string.h> char* stringToLower(const char* string){
char* tmp = (char*) malloc (strlen(string) + 1);
char* start = tmp;
while(*string != 0){
*(tmp++) = tolower(*(string++));
}
*tmp = 0;
return start;
} main(){
typedef int (fptroperation)(const char*, const char*);
int compare(const char* s1, const char* s2){
return strcmp(s1,s2);
} int compareIgnoreCase(const char* s1, const char* s2){
char* t1 =stringToLower(s1);
char* t2 =stringToLower(s2);
int result = strcmp(t1,t2); free(t1);
free(t2); return result;
}
void sort(char* array[], int size, fptroperation operation){
int swap = 1;
while(swap){
swap = 0;
int l = 0;
while(l<size-1){
if(operation(array[l],array[l+1])>0)
{
swap = 1;
char* tmp = array[l];
array[l] = array[l+1];
array[l+1] = tmp;
}
l++;
}
}
}
void display(char* names[], int size){
int i = 0;
while(i<size){
printf("%s ",names[i]);
i++;
}
printf("\n");
}
char* names[] = {"jack","rose","Titanic","hello","World"};
char* newnames[] = {"jack","rose","Titanic","hello","World"};
sort(names, 5, compare);
display(names,5);//Titanic World hello jack rose
sort(newnames, 5, compareIgnoreCase);
display(newnames, 5);//hello jack rose Titanic World
}

  这个示例通过使用函数指针来实现不同规则下的字符串比较工作。

深入理解C指针之五:指针和字符串的更多相关文章

  1. 06深入理解C指针之---指针操作和比较

    该系列文章源于<深入理解C指针>的阅读与理解,由于本人的见识和知识的欠缺可能有误,还望大家批评指教. 指针作为一种特殊类型的变量,必须遵守C语言中变量先声明后使用的原则.本节内容中指针的操 ...

  2. Swift3.0语言教程使用指针创建和初始化字符串

    Swift3.0语言教程使用指针创建和初始化字符串 Swift3.0语言教程使用指针创建和初始化字符串苹果的Swift团队花了不少功夫来支持C的一些基础特性.C语言中为我们提供了指针,Swift也不例 ...

  3. 【c实现,vc6调试通过】给出一字符串指针,计算出字符串指针中单词数

    #include <stdio.h> /* 给出一字符串指针,计算出字符串指针中单词数, 单词不包括'.',',',';','?','_','"',由0-9数字或26个字母组成 ...

  4. 00深入理解C指针之--- 指针之外

    该系列文章源于<深入理解C指针>的阅读与理解,由于本人的见识和知识的欠缺可能有误,还望大家批评指教. C语言从诞生之初就非常善于和硬件打交道,经过这么多年的发展之后,其灵活性和超强的特征是 ...

  5. 02深入理解C指针之---指针类型和值

    该系列文章源于<深入理解C指针>的阅读与理解,由于本人的见识和知识的欠缺可能有误,还望大家批评指教. 1.指针的类型: 可以在声明指针时,指定指针的类型,例如: (1)void *x  声 ...

  6. 10深入理解C指针之---指针运算和比较

    该系列文章源于<深入理解C指针>的阅读与理解,由于本人的见识和知识的欠缺可能有误,还望大家批评指教. 指针运算有很多种,主要有指针的声明*.指针的解引*.指针指向(*).或->.指针 ...

  7. 指针,数组,字符串的区别(高质量程序设计指南C++/C语言第7章)

    指针: 指针是变量,和平时的那些变量没有本质的差异,不同的只是它的值和类型,.,即解释方式 二进制层面:指针的值是内存单元的地址,而变量是引用内存单元值的别名 语言层面:指针的值就是变量的地址. 对象 ...

  8. 理解C语言中指针的声明以及复杂声明的语法

    昨天刚把<C程序设计语言>中"指针与数组"章节读完,最终把心中的疑惑彻底解开了.如今记录下我对指针声明的理解.顺便说下怎样在C语言中创建复杂声明以及读懂复杂声明. 本文 ...

  9. 这样子来理解C语言中指针的指针

    友情提示:阅读本文前,请先参考我的之前的文章<从四个属性的角度来理解C语言的指针也许会更好理解>,若已阅读,请继续往下看. 我从4个属性的角度来总结了C语言中的指针概念.对于C语言的一个指 ...

随机推荐

  1. 学习pthreads,创建和终止多线程

    更CPU多线程编程,通过笔者的研究发现,,pthreads使用日趋广泛.它是螺纹POSIX标准,它定义了一组线程的创建和操作API. 配置环境见上博客文章.配置环境后,只需要加入#include &l ...

  2. HSQLDB相关信息及用法汇总

    运行模式 说明 启动命令 JDBC例 内存(Memory-Only)模式 所有数据都在内存里操作.应用程序退出后则数据被销毁.无需另外启动HSQLDB Server 启动方式1:通过程序中首次调用Co ...

  3. 第十七章——配置SQLServer(4)——优化SQLServer实例的配置

    原文:第十七章--配置SQLServer(4)--优化SQLServer实例的配置 前言: Sp_configure 可以用于管理和优化SQLServer资源,而且绝大部分配置都可以使用SQLServ ...

  4. SQL SERVER 内存分配及常见内存问题(2)——DMV查询

    原文:SQL SERVER 内存分配及常见内存问题(2)--DMV查询 内存动态管理视图(DMV): 从sys.dm_os_memory_clerks开始. SELECT [type] , SUM(v ...

  5. 使用PF_PACKET和SOCK_RAW发送自己定义type以太网数据包

    本文介绍使用PF_PACKET和SOCK_RAW发送自己定义type以太网数据包,使用wireshare抓包能够获取到数据包,为Linux内核添加网络协议做铺垫. 先上码: #include < ...

  6. JavaScript面向对象旅程(下)

    JavaScript面向对象旅程 剪不断,理还乱,是离愁. 前面已经提到过新语言开发的两个步骤,分别是:一.定义基本的数据类型,完善结构化编程语言的设计:二.为函数类型绑定this的概念,好在对象的方 ...

  7. CentOS7 安装Hadoop集群环境

    先按照上一篇安装与配置好CentOS以及zookeeper http://www.cnblogs.com/dopeter/p/4609276.html 本章介绍在CentOS搭建Hadoop集群环境 ...

  8. python 基础知识点整理 和详细应用

    Python教程 Python是一种简单易学,功能强大的编程语言.它包含了高效的高级数据结构和简单而有效的方法,面向对象编程.Python优雅的语法,动态类型,以及它天然的解释能力,使其成为理想的语言 ...

  9. sql查询第二大的记录(转)

    问题: 数据库中人表有三个属性,用户(编号,姓名,身高),查询出该身高排名第二的高度.建表语句 create table users ( id ,) primary key, name ), heig ...

  10. ABP之动态WebAPI

    ABP之动态WebAPI ABP的动态WebApi实现了直接对服务层的调用(其实病没有跨过ApiController,只是将ApiController公共化,对于这一点的处理类似于MVC,对服务端的 ...