halisi7

一个专注技术的组织

0%

c与指针2

基本概念

1.三字母词

​ 三字母词就是几个字符的序列,合起来表示另一个字符。

1
2
3
??( [    ??< {    ??= #
  ??) ]    ??> }    ??/ \
  ??! |    ??’ ^    ??- ~
1
printf("Delete file (are you really sure??): " );

输出

1
Delete file (are you really sure ]:

2. typedef

1
typedef   char  *ptr_to_char;

这个声明把标识符ptr_to_char作为指向字符的指针类型的新名字。你可以像使用任何预定义名字一样在下面的声明中使用这个新名字。例如:

1
ptr_to_char   a;

好处:

  1. 使用typedef声明类型可以减少使声明变得又臭又长的危险,尤其是那些复杂的声明 。
  2. 而且,如果你以后觉得应该修改程序所使用的一些数据的类型时,修改一个typedef声明比修改程序中与这种类型有关的所有变量(和函数)的所有声明要容易得多。

3. goto语句

1
goto 语句标签;

例如:

image-20220326121510141

4.零为假,非零为真。

5.间接访问操作符

通过一个指针访问它所指向的地址的过程称为间接访问

这个用于执行间接访问的操作符是单目操作符 * 。

6. NULL指针

NULL指针——表示不指向任何东西。

要使一个指针变量为NULL,你可以给它赋一个零值。

为了测试一个指针变量是否为NULL,你可以将它与零值进行比较。

1
2
3
int *p=0;
if(p==NULL)
printf("Delete file (are you really sure ??): ");

7. stdarg宏

可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件,它是标准库的一部分。

这个头文件声明了一个类型va_list和三个宏——va_start、va_arg和va_end。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
** 计算指定数量的值的平均值。
*/

#include <stdarg.h>

float
average( int n_values, ... )
{
      va_list  var_arg;
//va_list是一个类型。
//var_arg的变量,它用于访问参数列表的未确定部分。
      int  count;
      float sum = 0;

      /*
      ** 准备访问可变参数。
      */
      va_start( var_arg, n_values );
//var_arg通过调用va_start来初始化。
//它的第1个参数是va_list变量的名字,第2个参数是省略号前
//最后一个有名字的参数。
      /*
      ** 添加取自可变参数列表的值。
      */
      for( count = 0; count < n_values; count += 1 ){

         sum += va_arg( var_arg, int );
      }

      /*
      ** 完成处理可变参数。
      */
      va_end( var_arg );
//当访问完毕最后一个可变参数之后,我们需要调用va_end。
      return sum / n_values;
}

char和short类型的实参被转换为int类型,float类型的实参被转换为double类型。所以你在va_arg中使用后面这些类型时应该小心。

8. *ap+6相当于表达式array[2]+6。

9. 2[array]等于*( array + 2 ),array为数组。

1
2
3
int a[3]={1,2,3};
int t=1[a];
printf("%d",t); //2

这个诡异技巧之所以可行,缘于C实现下标的方法。对编译器来说,这两种形式并无差别。但是,你绝不应该编写2[array],因为它会大大影响程序的可读性。

下标绝不会比指针更有效率,但指针有时会比下标更有效率。

10.字符数组初始化

1
char message[] = { 'H', 'e', 'l', 'l', 'o', 0 };

方式二

1
char message[] = "Hello";//双引号括起来
1
2
char   message1[] = "Hello";//初始化一个字符数组的元素
char   *message2 = "Hello";//一个字符串常量

11.二维数组

1
2
int  matrix[3][10];
matrix //1

1.image-20220327111428545

1
matrix + 1 //2

2.image-20220327111518184

1
2
int  vector[10], *vp = vector;//合法
int matrix[3][10], *mp = matrix;//不合法
  • 原因:mp的初始化是不正确的,因为matrix并不是一个指向整型的指针,而是一个指向整型数组的指针。

12. 字符串的长度并不包括NUL字节。

string.h包含了使用字符串函数所需的原型和声明。

1
2
3
4
if( strlen( x ) >= strlen( y ) ) ...
if( strlen( x ) – strlen( y ) >= 0 ) ...//strlen的结果是个
//无符号数,所以操作符>=左边的表达式也将是无符号数,而无符号数绝
//不可能是负的。
  • 上面的两者并不相等!

库函数strlen的原型如下:

1
2
3
size_t strlen( char const *string );
//注意strlen返回一个类型为size_t的值。这个类型是在头文件stddef.h
//中定义的,它是一个无符号整数类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
** 计算字符串参数的长度。
*/
#include <stddef.h>

size_t
strlen( char const *string )
{
  int length;

  for( length = 0; *string++ != '\0'; )
    length += 1;

  return length;
}

13.复制字符串

用于复制字符串的函数是strcpy,它的原型如下所示:

1
char  *strcpy( char *dst, char const *src );
1
2
3
4
5
6
char   message[] = "Original message";
...
strcpy( message, "A different message" );
/*第2个字符串太长了,无法容纳于message字符数组中。因此,
strcpy函数将侵占数组后面的部分内存空间,改写原先恰好存储在那里的
变量。*/

14.连接字符串

要想把一个字符串添加(连接)到另一个字符串的后面,你可以使用strcat函数。它的原型如下:

1
char  *strcat( char *dst, char const *src );

和前面一样,程序员必须保证目标字符数组剩余的空间足以保存整个源字符串。

返回值

strcpy和strcat都返回它们第1个参数的一份拷贝,就是一个指向目标字符数组的指针。由于它们返回这种类型的值,所以你可以嵌套地调用这些函数,如下面的例子所示:

1
2
3
strcat( strcpy( dst, a ), b );
/*strcpy首先执行。它把字符串从a复制到dst并返回dst。然后这个返回值
成为strcat函数的第1个参数,strcat函数把b添加到dst的后面。*/

15.字符串比较

库函数strcmp用于比较两个字符串,它的原型如下:

1
2
3
int  strcmp( char const *s1, char const *s2 );
/*如果s1小于s2,strcmp函数返回一个小于零的值。如果s1大于s2,函数
返回一个大于零的值。如果两个字符串相等,函数就返回零。*/

16.长度受限的字符串函数

image-20220327120125021

1.strncpy把源字符串的字符复制到目标数组。然而,它总是正好向dst写入len个字符。如果strlen( src )的值小于len,dst数组就用额外的NUL字节填充到len长度。如果strlen( src )的值大于或等于len,那么只有len个字符被复制到dst中。注意!它的结果将不会以NUL字节结尾 。

使用了一个不是以NUL字节结尾的字符序列,会发生什么情况呢?

strlen函数将无法知道NUL字节是没有的,所以它将继续进行查找,一个字符接一个字符,直到它发现一个NUL字节为止。或许它找了几百个字符才找到,而strlen函数的这个返回值从本质上说是一个随机数。或者,如果函数试图访问系统分配给这个程序以外的内存范围,程序就会崩溃。

这个问题只有当你使用strncpy函数创建字符串,然后或者对它们使用str开头的库函数,或者在printf中使用%s格式码打印它们时才会发生。

例如,考虑下面这个代码段:

image-20220327120331838

如果name的内容可以容纳于buffer中,最后那个赋值语句没有任何效果。但是,如果name太长,这条赋值语句可以保证buffer中的字符串是以NUL结尾的。以后对这个数组使用strlen或其他不受限制的字符串函数将能够正确工作。

2.strncat也是一个长度受限的函数,但它和strncpy存在不同之外。它从src中最多复制len个字符到目标数组的后面。但是,strncat总是在结果字符串后面添加一个NUL字节,而且它不会像strncpy那样对目标数组用NUL字节进行填充。注意目标数组中原先的字符串并没有算在strncat的长度中。strncat最多向目标数组复制len个字符(再加一个结尾的NUL字节),它才不管目标参数除去原先存在的字符串之后留下的空间够不够。

3.strncmp也用于比较两个字符串,但它最多比较len个字节。如果两个字符串在第len个字符之前存在不相等的字符,这个函数就像strcmp一样停止比较,返回结果。如果两个字符串的前len个字符相等,函数就返回零。

17.查找一个字符

在一个字符串中查找一个特定字符最容易的方法是使用strchr和strrchr函数,它们的原型如下所示:

image-20220327120747965

1.strchr在字符串str中查找字符ch第1次出现的位置,找到后函数返回一个指向该位置的指针。如果该字符并不存在于字符串中,函数就返回一个NULL指针。

2.trrchr的功能和strchr基本一致,只是它所返回的是一个指向字符串中该字符最后一次出现的位置.

3.注意这里大小写是有区别的。

image-20220327121255307

18.查找任何几个字符

strpbrk是个更为常见的函数。它并不是查找某个特定的字符,而是查找任何一组字符第1次在字符串中出现的位置。它的原型如下:

1
char  *strpbrk( char const *str, char const *group );

这个函数返回一个指向str中第1个匹配group中任何一个字符的字符位置。如果未找到匹配,函数返回一个NULL指针。

image-20220327121311754

19.查找一个子串

为了在字符串中查找一个子串,我们可以使用strstr函数,它的原型如下:

1
char *strstr( char const *s1, char const *s2 );

这个函数在s1中查找整个s2第1次出现的起始位置,并返回一个指向该位置的指针。如果s2并没有完整地出现在s1的任何地方,函数将返回一个NULL指针。如果第2个参数是一个空字符串,函数就返回s1。

标准库中并不存在strstr或strpbrk函数。不过,如果你需要它们,它们是很容易实现的。程序9.2显示了一种实现strstr的方法。这个技巧同样也可以用于实现strpbrk。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
** 在字符串s1中查找字符串s2最右出现的位置,并返回一个指向该位置的指针。
*/
#include <string.h>

char*
my_strrstr( char const *s1, char const *s2 )
{
    register char*last;
    register char*current;
    /*
    ** 把指针初始化为我们已经找到的前一次匹配位置。
    */
    last = NULL;

    /*
    **只在第2个字符串不为空时才进行查找,如果S2为空,返回NULL。
    */
if( *s2 != '\0' ){
      /*
      ** 查找s2在s1中第1次出现的位置。
      */
      current = strstr( s1, s2 );

    /*
    ** 我们每次找到字符串时,让指针指向它的起始位置。然后查找该字符串下一个匹配位置。
    */
          while( current != NULL ){
            last = current;
            current = strstr( last + 1, s2 );
          }
      }

      /* 返回指向我们找到的最后一次匹配的起始位置的指针。*/
      return last;
}
  • 实测:标准库里是有这两个函数的不用自己写.

20.查找一个字符串前缀

strspn和strcspn函数用于在字符串的起始位置对字符计数。它们的原型如下所示:

1
2
size_t strspn( char const *str, char const *group );
size_t strcspn( char cosnt *str, char const *group );

group字符串指定一个或多个字符。strspn返回str起始部分匹配group中任意字符的字符数。例如,如果group包含了空格、制表符等空白字符,那么这个函数将返回str起始部分空白字符的数目。str的下一个字符就是它的第1个非空白字符。

image-20220327123341760

image-20220327123357521

strcspn函数和strspn函数正好相反,它对str字符串起始部分中不与group中任何字符匹配的字符进行计数。返回起始到匹配的字符数.

如果你使用“ \n\r\f\t\v”作为group参数,这个函数将返回第1个参数字符串起始部分所有非空白字符的值。

1
2
3
4
char q[]="hello world";
int len;
len=strspn(q,"hellq");//返回起始到不匹配的字符数.
printf("%d",len);//4
1
2
3
4
char q[]="hello world";
int len;
len=strcspn(q,"oq");//返回起始到匹配的字符数.
printf("%d",len);//4

21.查找标记

这个任务正是strtok函数所实现的功能。它从字符串中隔离各个单独的称为标记(token)的部分,并丢弃分隔符。它的原型如下:

1
char  *strtok( char *str, char const *sep );

strtok找到str的下一个标记,并将其用NUL结尾,然后返回一个指向这个标记的指针。

当strtok函数执行任务时,它将会修改它所处理的字符串。如果源字符串不能被修改,那就复制一份,将这份拷贝传递给strtok函数。

如果字符串内不存在更多的标记,strtok函数就返回一个NULL指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
** 从一个字符数组中提取空白字符分隔的标记并把它们打印出来(每行一个)。
*/
#include <stdio.h>
#include <string.h>
void print_tokens( char *line )
{
     static char whitespace[] = " \t\f\r\v\n";
     char *token;

     for( token = strtok( line, whitespace );
       token != NULL;
       token = strtok( NULL, whitespace ) )
       printf( "Next token is %s\n", token );
}

由于strtok函数保存它所处理的函数的局部状态信息,所以你不能用它同时解析两个字符串。因此,如果for循环的循环体内调用了一个在内部调用strtok函数的函数,不能嵌套使用.

打赏一下作者~ ฅ( ̳• ◡ • ̳)ฅ