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

原文:深入理解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
}

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

时间: 2024-11-03 00:03:38

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

深入理解c++指针的指针和指针的引用_C 语言

展示一下使用指针的指针和指针的引用修改传递给方法的指针,以便更好的使用它.(这里说的指针的指针不是一个二维数组) 为什么需要使用它们 当我们把一个指针做为参数传一个方法时,其实是把指针的复本传递给了方法,也可以说传递指针是指针的值传递. 如果我们在方法内部修改指针会出现问题,在方法里做修改只是修改的指针的copy而不是指针本身,原来的指针还保留着原来 的值.我们用下边的代码说明一下问题: int m_value = 1; void func(int *p) { p = &m_value; } i

C++ 用指向指针的指针的方法对5个字符串排序并输出时遇到的问题

问题描述 C++ 用指向指针的指针的方法对5个字符串排序并输出时遇到的问题 //用指向指针的指针的方法对5个字符串排序并输出 void sort(char **p) { char* temp=new char;//为什么没有长度呢?这么可以没有长度呢?? for(int j=0;j<5;j++) { for(int k=j;k<5;k++) { if(strcmp(p[j],p[k])>0) { temp=p[j]; p[j]=p[k]; p[k]=temp; } } } } int m

深入理解数组指针与指针数组的区别_C 语言

数组指针与指针数组的区别在于:数组指针p是一个指针,而指针数组p是一个存放N个指针变量的数组. 一.数组指针int (*p)[n]重点:()优先级高([].()的优先级是一样的,但它们的方向是从左至右的,所以先运行括号里的*p),首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长.也就是说执行p+1时,p要跨过n个整型数据的长度(n*sizeof(int)).如要将二维数组赋给一指针,应这样赋值:       int a[3][4];       int (

C/C++中字符指针数组及指向指针的指针的含义

就指向指针的指针,很早以前在说指针的时候说过,但后来发现很多人还是比较难以理解,这一次我们再次仔细说一说指向指针的指针. 先看下面的代码,注意看代码中的注解: #include <iostream>#include <string>using namespace std; void print_char(char* array[],int len);//函数原形声明void main(void){//-----------------------------段1----------

函数指针与指针函数的学习总结

函数指针是指向函数的指针,指针函数是指一个函数的返回值是一个指针,但下面的几道题还是感觉很迷惑.各位能否讲的详细点呢? (1) float(**def)[10]   def是什么? (2) double*(*gh)[10]   gh是什么? (3) double(*f[10])()   f是什么? (4) int*((*b)[10])    b是什么?这样老感觉有点乱,有什么窍门可以记得并理解的清楚一点么? ======================解答:   (1) def是一个指针, 指向

指针、指针变量与内存空间——解惑

指针.指针变量与内存空间--解惑              一年前,我曾今在ChinaUnix写过一篇博客<关于定义一个指针与指针变量>然后搬家来了CSDN.这篇博客中写道了很多关于指针和指针变量以及空间分配的一些常见错误.但是,到现在为止,无论是CSDN的网友还是一些已经工作的同事以及大三大四的学生,陆陆续续的有人在问我关于指针与指针变量还有空间分配问题.我想在这里再写一篇博客解惑指针     问题1 int idata = 5; int *p = idata//为什么这里不对? 既然通常*

Android For JNI(三)——C的指针,指针变量,指针常见错误,值传递,引用传递,返回多个值

Android For JNI(三)--C的指针,指针变量,指针常见错误,值传递,引用传递,返回多个值 C中比较难的这一块,大概就是指针了,所以大家还是多翻阅一下资料,当然,如果只是想了解一下,看本篇也就够了,不过我也尽量陈述的很详细 一.指针 要说指针,其实通俗易懂的话来说,他应该是保存内存地址的一个变量,我们来看一下小例子 #include <stdio.h> #include <stdlib.h> main(){ //int 变量 int i ; i = 5; //打印i的值

函数指针与指针函数的学习总结_C 语言

函数指针是指向函数的指针,指针函数是指一个函数的返回值是一个指针,但下面的几道题还是感觉很迷惑.各位能否讲的详细点呢? (1) float(**def)[10]   def是什么?(2) double*(*gh)[10]   gh是什么?(3) double(*f[10])()   f是什么?(4) int*((*b)[10])    b是什么?这样老感觉有点乱,有什么窍门可以记得并理解的清楚一点么? ======================解答:   (1) def是一个指针, 指向的对象

指针数组,数组指针,指针函数,函数指针,二级指针详解

先看个简单的:char *p,这定义了一个指针,指针指向的数据类型是字符型,char  *(p)定义了一个指针P: char *p[4], 为指针数组,由于[]的优先级高于*,所以p先和[]结合,p[]是一个数组,暂时把p[]看成是q,也就是char *(q),定义了一个指针q,只不过q是一个数组罢了,故定义了一个数组,数组里面的数据是char *的,所以数组里面的数据为指针类型.所以char *p[4]是四个指针,这四个指针组成了一个数组,称为指针数组,既有多个指针组成的数组. char(*p