前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >程序员C语言快速上手——基础篇(四)

程序员C语言快速上手——基础篇(四)

作者头像
arcticfox
修改2019-06-26 19:19:25
7320
修改2019-06-26 19:19:25
举报
  • 基础语法
    • 简单数组
      • 声明数组
      • 初始化数组
      • 下标访问
      • 计算数组长度
      • 数组使用小结
    • 字符与字符串
      • char 字符
      • 宽字符
      • 字符串 (String)
        • 字符串与普通数组的区别
      • 小拓展:
      • 字符串的常用函数
        • 字符串长度
        • 比较字符串内容
        • 字符串的复制
        • 字符串的拼接

基础语法

简单数组

把具有相同类型的若干个数据按一定顺序组织起来,这些同类数据元素的集合就称为数组。数组元素可以是基本数据类型,也可以是结构体类型。注意,C语言中的数组与其他编程语言的数组或列表有相似性,但本质上又有不同。

声明数组

代码语言:javascript
复制
1  // 声明格式:类型 数组变量名[长度]
2  // 声明数组时需指明元素类型和长度(元素个数),且[]中的长度必须为常量
3  int arr[10];

初始化数组

C语言数组在使用前应当初始化,否则数组中的数据是不确定的,由此会造成一些不可预知的问题。

代码语言:javascript
复制
 1  // 声明的同时,使用字面量初始化。即大括号初始化
 2  int arr[10] = {0,1,2,3,4,5,6,7,8,9};
 3
 4  // 可以只指定部分元素的值,剩下的元素将自动使用0值初始化
 5   int arr[10] = {0,1,2,3,4};   //数组元素:0,1,2,3,4,0,0,0,0,0
 6
 7  // 使用大括号初始化时,中括号中的长度可以省略,编译器将按照实际的个数来确定数组长度
 8   int arr[] = {0,1,2,3,4,5,6,7,8,9};
 9
10  // 不需要指定每个元素具体值,仅做零值初始化时,可以使用如下写法
11  int arr[10] = {0};     // 数组的每个元素都会被初始化为0

需要注意,使用大括号初始化数组时,大括号中不能为空,至少要写一个值。如int arr[10] = {}; 语法错误!

下标访问

要访问数组中的任意一个元素,都可以通过数组下标访问。因为数组是有顺序的,下标就是元素的序号。但是要注意,数组的第一个元素的序号是0,也就是说下标是从0开始的。

代码语言:javascript
复制
1  int a[6] = {12,4,5,6,7,8};
2
3  // 打印数字中的元素。使用: 数组变量[下标]的格式获取元素
4  printf("%d \n",a[0]);
5  printf("%d \n",a[1]);

在这里插入图片描述 遍历数组

代码语言:javascript
复制
 1  int a[6] = {12,4,5,6,7,8};
 2
 3  // 使用for 循环来访问数组中的每一个元素
 4  for(int i=0;i<6;i++){
 5    printf("%d \n",a[i]);
 6 }
 7
 8  // 使用for循环修改数组元素
 9  for(int i=0;i<6;i++){
10    a[i] = i+2;
11    printf("%d \n",a[i]);
12 }

要注意,在访问数组元素时,[]括号中的下标可以是整型变量。

计算数组长度

虽然我们可以明确的知道数组的长度,但有时候我们需要编写更友好更易于维护的代码,例如数组的长度经常修改,则我们需要修改每一处使用数组长度的地方,不易于维护,因此我们需要能动态的计算出数组长度,而不是将长度写死。

前面我们已经多次使用过sizeof运算符,该运算符可以获取类型或变量的内存大小,那么我们可以使用它获得数组总内存大小(即数组占用多少内存),然后用总内存大小除以每一个元素占用的内存大小,就可以获得数组的长度了。由于数组存放的都是同一种类型数据,因此每一个元素占用的内存大小都是固定且相等的。

代码语言:javascript
复制
1  int a[6] = {12,4,5,6,7,8};
2
3  // 计算数组长度。数组总内存大小/每个元素内存大小
4  int len = sizeof(a)/sizeof(int);
5  for(int i=0;i<len;i++){
6    printf("%d \n",a[i]);
7  }

如上例,当修改数组大小时,只需要修改数组a的声明大小,其他地方不需做任何修改。

数组使用小结

  1. 声明数组时,数组长度必须使用常量指定
  2. 数组应当先初始化再使用
  3. 数组的下标(序号)是从0开始的
  4. 访问数组时必须做边界检查。例如数组a的长度为5,则使用a[5]访问是错误的。a[5]表示的是数组的第6个元素,访问超出数组长度的元素会导致程序异常退出。如果数组长度是n,则当a[i]访问时,应当保证i &lt; n

字符与字符串

如果对于字符、字符编码这些不是非常清楚,或者说是一知半解,建议先看看博主的另一篇科普文章,对与字符与字符编码有了更深入的理解再学习以下内容。

《字符编码的前世今生——一文读懂字符编码》

char 字符

C语言中字符是非常简单的,同时也意味着非常原始!

代码语言:javascript
复制
1  // 声明一个字符变量
2  char s = 'a';

在C语言中,字符类型的字面量是单引号括起来的一个字符,注意,字符不是字符串,它只能写一个。且char类型的字符只能表示ASCII表中的字符。实际上,C语言的char就是一个整数,它的范围是0~127

代码语言:javascript
复制
1    char s = 'a';
2    char s1 = 97;
3
4    // 可以看到,s和s1打印的结果完全相同
5    printf("%c \n",s);
6    printf("%c \n",s1);
7
8    // 以整数形式打印字符`a`
9    printf("%d \n",s);

char保存的这个整数也就是每个字符对应的编号,具体的内容我们可以查看ASCII

在这里插入图片描述 仔细观察这张表,我们可以发现一个好玩的规律,所有大写字母的编号都比它对应的小写字母小32。例如a的编号是97,则A的编号是97-32=65。发现这个规律,我们就能非常简单的实现大小写字母的转换了。

代码语言:javascript
复制
1    char s1 = 'c';
2    char s2 = 'G';
3
4    printf("%c \n", s1-32); //小写转大写
5    printf("%c \n", s2+32); //大写转小写

打印结果

代码语言:javascript
复制
1  C 
2  g 

由于char本质上是整数类型,因此可以直接进行算术运算。

宽字符

有些朋友已经发现了,char类型是C语言发展的早期,未考虑地区性字符的产物。简单说就是不能表示中文。直接char s1 = '中';这样写编译会报错的,后续当然是要出台补救措施,宽字符就是补救措施的产物。需要注意,这里宽字符概念仅作为知识拓展,这种解决方案基本被时代所遗弃,仅部分陈旧项目或某些系统内部编码使用。

代码语言:javascript
复制
 1  #include <stdio.h>
 2
 3  // 使用宽字符,需包含头文件
 4  #include <wchar.h>
 5
 6  int main(){
 7    // 声明宽字符,字面量前需加上大写L   
 8     wchar_t  s = L'中';
 9
10    printf("size is %d \n",sizeof(wchar_t));
11    printf("code = %d \n",s);
12  }
13

打印结果:

代码语言:javascript
复制
1  size is 2 
2  code = 20013

可以看到,这里宽字符的编号是20013,显然一个字节是存不了这么大的整数的,因此宽字符使用两个字节来存字符的编号。这就是为什么被称为宽字符的原因,它比char要宽,使用两个字节16位表示。

在中国大陆区的Window系统中,默认使用的编码表是GBK,并且Windows还使用一种页的概念来表示编码表,而GBK编码表对应的就是page 936,也就是第936页表示GBK编码。如要查看GBK编码表,可将page 936的内容下载下来查看,链接地址 复制该连接地址,选择目标另存为即可下载该txt文件

打印输出宽字符,比直接打印char要麻烦

代码语言:javascript
复制
 1  #include <stdio.h>
 2  #include <wchar.h>
 3
 4  // 使用setlocale需包含头文件
 5  #include <locale.h>
 6
 7  int main(){
 8    wchar_t  s = L'中';
 9
10    // 需先设置本地的语言环境,第二个参数传"",表示使用本机默认字符集
11    setlocale(LC_ALL, "");
12
13    // 两种打印宽字符的方式,其中wprintf为宽字符专用函数
14    wprintf(L"%lc \n",s);
15    printf("%lc \n",s);
16  }
17

字符串 (String)

所谓字符串,顾名思义,就是将许多单个字符串成一串。既然要把多个字符串起来,当然就需要用到上面说的数组了,存放char类型元素的数组,被称为字符数组。由于C语言没有专门为字符串提供单独的类型,因此只能使用字符数组的方式来表示字符串,这是与其他编程语言很大不同的地方,也是比较繁琐的地方,如果说其他高级语言是自动挡的小轿车,那么C语言就是手动挡的轿车。

声明并初始化字符串

代码语言:javascript
复制
 1    //1. 与普通数组相同,用花括号初始化
 2    char str1[30] = {'h','e','l','l','o','w','o','r','l','d'};
 3    char str2[20] = {"hello world"};    //字符数组的特殊方式
 4
 5    //2. 字符数组特有的方式。使用英文双引号括起来的字符串字面量初始化
 6    char str3[20] = "hello world";
 7
 8    //3. 省略数组长度
 9    char str4[] = {"hello world"};
10
11    //4. 省略数组长度,并使用字符串字面量初始化
12    char str5[] = "hello world";

在C语言中声明字符串,推荐以上第4种方式,它具有简洁且能避免出错的优点。

字符串与普通数组的区别

在C语言中,虽说字符串是用字符数组来表示的,但是字符串和普通字符数组仍然是不同的,这两者的区别可以简单总结为如下三点

  1. C语言字符串规定,结尾必须包含一个特殊字符'\0',我们查询一下ASCII表可知,该字符属于控制字符,即无法打印显示出来的字符,它在ASCII表中的编号是0,即表中的第一个字符NUL
  2. 字符串的实际长度(即字符的个数)比字符数组的长度小1。
  3. 声明的同时,数组只能使用花括号初始化,而字符串可以使用双引号括起来的字面量初始化。

现在通过代码验证以上结论

代码语言:javascript
复制
 1    // 请注意,以下代码会造成无法预知的错误。不可为!
 2    char s1[3] = {'a','b','c'};
 3    printf(" %s \n",s1);
 4
 5    // 手动添加字符串结束符'\0'或整数0。正确
 6    char s2[4] = {'a','b','c','\0'};
 7    printf(" %s \n",s2);
 8
 9    //只要预留结束符的位置,编译器会自动帮我们添加,无需手动
10    char s3[4] = {'a','b','c'};
11    char s4[4] = "abc";
12
13    printf("s3=%s s4=%s \n",s3,s4);

通过以上代码验证,我们就会发现,使用char str5[] = "hello world";方式声明并初始化字符串是最好的做法,既简洁,也无需操心是否预留了字符串结束符的位置,因为编译器会自动帮我们计算好。最后再强调一次,由于字符串末尾会自动添加\0结束符,因此字符串的实际长度会比字符数组的长度小1。

声明时不初始化

代码语言:javascript
复制
 1   char str[20];
 2    /*
 3        错误的赋值方式!!!
 4        str = "abc";
 5        str = {"abc"};
 6    */
 7
 8   // 不规范的使用方式
 9   str[0]='a';
10   str[1]='b';
11   str[2]='c';
12
13   printf("%s",str);

以上代码是不规范的使用方式。当我们声明字符数组时未初始化就使用了,则编译器不会自动为我们添加结束符\0,使用微软的VC编译器进行编译后,直接出现了乱码情况,虽然GCC不会出乱码,但也存在不可预知的问题。

代码语言:javascript
复制
1abc烫烫烫烫烫烫烫烫烫烫特3臋H?

正确的做法是在未初始化的情况下,使用字符串数组应手动添加结束符

代码语言:javascript
复制
1   char str[20];
2
3   str[0]='a';
4   str[1]='b';
5   str[2]='c';
6   str[3]='\0';
7
8   printf("%s\n",str);

当然,除了手动添加结束符号,还可以使用C语言标准库的函数来自动初始化数组。这是一种更常用的做法

代码语言:javascript
复制
 1  #include <stdio.h>
 2  #include <string.h>   // 需要包含string.h头文件
 3
 4  int main(){
 5    char str[20];
 6    // 将数组初始化化为指定的值,这里指定0,第三个参数是数组的内存大小
 7    memset(str, 0, sizeof(str));
 8
 9    str[0] = 'a';
10    str[1] = 'b';
11    str[2] = 'c';
12
13    printf("%s", str);
14
15    return 0;
16  }

小拓展:

使用VC编译器,未初始化的数组为什么会出现“烫烫烫”? 因为VC编译器默认会干一件事情,将未初始化的字符数组,使用十六进制数0xcc进行填充

在这里插入图片描述 观察以上内存布局图,可知前三个元素分别是十六进制0x610x620x63,转换成十进制就是97、98、99,正好是a、b、c的ASCII码编号,剩余数组元素则默认都是0xcc,而它的十进制则是204,显然已经超出了ASCII码表的范围,Windows默认使用GBK码表,用两个字节表示一个汉字。这时候我们去查询page 936表,可发现两个cc字节合起来就是汉字

还可以查GBK的表,首字节cc的平面表如下,然后根据尾字节去查具体对应的汉字,这里尾字节也是cc

在这里插入图片描述 除了被填充成cc,乱码还与数组越界有关。因为没有字符串结束符\0,使用printf打印的时候,它并不知道应该在哪儿结束,因为内存都是连成一片的,超过str[20]的20个元素范围,后面还有内存空间,因此乱码 abc烫烫烫烫烫烫烫烫烫烫特3臋H?明显超出了20个char的范围,将其他的内存内容也打印了。这就好比你家住18号,你不仅把18号的门打开了,还把隔壁19号的门也撬开了。

字符串的常用函数

C语言虽然是手动挡的,但也为我们提供了一些不太完美的标准库函数,虽然这些函数多多少少都存在一些坑,但也聊胜于无,总比我们事事躬亲要强许多。要想使用字符串库函数,需要包含string.h头文件。

字符串长度
  • strlen
代码语言:javascript
复制
 1   #include <stdio.h>
 2   #include <string.h>
 3
 4   int main(void){
 5    char str[]= "hello world!";
 6
 7    // 动态计算str数组的长度
 8    printf("array size is %d\n",sizeof(str)/sizeof(char));
 9
10    // 获取字符串的长度
11    int len = strlen(str);
12    printf("string size is %d\n",len);
13
14    return 0;
15  }

打印结果:

代码语言:javascript
复制
1  array size is 13
2  string size is 12

可见str数组共用13个元素,但只有12个有效字符,最后一个为\0结束符

比较字符串内容

当我们要判断两个字符串是否相同时,是不能直接使用比较运算符==操作的

代码语言:javascript
复制
1    char str1[]= "hello";
2    char str2[]= "hello";
3
4    // ==比较的是两个数组的地址,而不是内容,结果与预期不符
5    printf("%d\n",str1 == str2);
  • strcmp
代码语言:javascript
复制
 1  #include <stdio.h>
 2  #include <string.h>
 3
 4  int main(void){
 5    char str1[]= "hello";
 6    char str2[]= "hello";
 7
 8    // strcmp的返回值等于0时,表示两个字符串内容相同,否则不同
 9    if (strcmp(str1,str2) == 0){
10       printf("str1 == str2\n");
11    }else{
12        printf("str1 != str2\n");
13    }
14
15    char str3[]= "bruce";
16    char str4[]= "hello";
17
18    if (strcmp(str3,str4) == 0){
19       printf("str1 == str2\n");
20    }else{
21        printf("str1 != str2\n");
22    }
23
24    return 0;
25  }

打印结果:

代码语言:javascript
复制
1  str1 == str2
2  str3 != str4
字符串的复制
  • strncpy 还可使用该函数为字符数组进行初始化
代码语言:javascript
复制
 1  #include <stdio.h>
 2  #include <string.h>
 3
 4  int main(void){
 5    char str1[100];
 6
 7    // 将字符串复制到指定的字符数组中,并自动复制结束符。第一个参数就是目的地
 8    // 第三个参数需指定复制的长度,这里指定目标数组的大小,表示如果超过这个长度则以这个长度为止
 9    strncpy(str1,"Greetings from C",sizeof(str1));
10    printf("str1=%s\n",str1);
11
12    // 将str1的内容复制到str2中
13    char str2[50];
14    strncpy(str2,str1,sizeof(str2));
15    printf("str2=%s\n",str2);
16    return 0;
17  }

暗坑 strncpy函数存在一个问题,如果被复制的字符串长度太长,超过了目的数组的长度,则将目的数组填充满为止,但是这种情况下就不会添加\0结束符,导致存在不可预知的问题。

代码语言:javascript
复制
 1  #include <stdio.h>
 2  #include <string.h>
 3
 4  int main(void){
 5    char str1[10];
 6
 7    // 字符串超过str1的长度,导致str1没有结束符
 8    strncpy(str1,"Greetings from C", sizeof(str1));
 9    printf("str1=%s\n",str1);   // 乱码
10
11    char str2[10];
12
13    // 更安全合理的做法,始终为结束符预留一个位置
14    strncpy(str2,"Greetings from C", sizeof(str2)-1);
15    printf("str2=%s\n",str2); // 字符串虽被截断,但是有结束符,安全!
16    return 0;
17}
字符串的拼接

在其他语言中,通常只需要简单的使用+号就能拼接字符串,但是C语言就显得繁琐

  • strncat
代码语言:javascript
复制
 1  #include <stdio.h>
 2  #include <string.h>
 3
 4  int main(void){
 5    char str1[100] = "hello";
 6
 7    // 将第二个参数的内容追加到第一个参数的后面,相当于将两者拼接
 8    // 第三个参数为拷贝的长度,类似strncpy,
 9    // 这里计算数组的总长度减去字符串的长度,求得str1剩余空间的长度
10    strncat(str1," world!",sizeof(str1)/sizeof(char)-strlen(str1));
11    printf("str1=%s\n",str1);
12
13    return 0;
14  }

strncpy函数相似,这里的暗坑也是目的地数组的空间不足导致丢失结束符的问题,因此应当预留结束符的位置

代码语言:javascript
复制
1  strncat(str1," world!",sizeof(str1)/sizeof(char)-strlen(str1) - 1);

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程之路从0到1 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基础语法
    • 简单数组
      • 声明数组
      • 初始化数组
      • 下标访问
      • 计算数组长度
      • 数组使用小结
    • 字符与字符串
      • char 字符
      • 宽字符
      • 字符串 (String)
      • 小拓展:
      • 字符串的常用函数
相关产品与服务
云函数
云函数(Serverless Cloud Function,SCF)是腾讯云为企业和开发者们提供的无服务器执行环境,帮助您在无需购买和管理服务器的情况下运行代码。您只需使用平台支持的语言编写核心代码并设置代码运行的条件,即可在腾讯云基础设施上弹性、安全地运行代码。云函数是实时文件处理和数据处理等场景下理想的计算平台。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档