程序员C语言快速上手——基础篇(四) 刺骨的言语ヽ痛彻心扉 2022-01-23 13:25 230阅读 0赞 ### 文章目录 ### * 基础语法 * * 简单数组 * * 声明数组 * 初始化数组 * 下标访问 * 计算数组长度 * 数组使用小结 * 字符与字符串 * * char 字符 * 宽字符 * 字符串 (String) * * 字符串与普通数组的区别 * 小拓展: * 字符串的常用函数 * * 字符串长度 * 比较字符串内容 * 字符串的复制 * 字符串的拼接 * 欢迎关注我的公众号:编程之路从0到1 **本专栏已发布配套视频内容,请查看公众号视频专辑《程序员的C》** # 基础语法 # ## 简单数组 ## 把具有相同类型的若干个数据按一定顺序组织起来,这些同类数据元素的集合就称为**数组**。数组元素可以是基本数据类型,也可以是结构体类型。注意,C语言中的数组与其他编程语言的数组或列表有相似性,但本质上又有不同。 ### 声明数组 ### // 声明格式:类型 数组变量名[长度] // 声明数组时需指明元素类型和长度(元素个数),且[]中的长度必须为常量 int arr[10]; ### 初始化数组 ### C语言数组在使用前应当初始化,否则数组中的数据是不确定的,由此会造成一些不可预知的问题。 // 声明的同时,使用字面量初始化。即大括号初始化 int arr[10] = { 0,1,2,3,4,5,6,7,8,9}; // 可以只指定部分元素的值,剩下的元素将自动使用0值初始化 int arr[10] = { 0,1,2,3,4}; //数组元素:0,1,2,3,4,0,0,0,0,0 // 使用大括号初始化时,中括号中的长度可以省略,编译器将按照实际的个数来确定数组长度 int arr[] = { 0,1,2,3,4,5,6,7,8,9}; // 不需要指定每个元素具体值,仅做零值初始化时,可以使用如下写法 int arr[10] = { 0}; // 数组的每个元素都会被初始化为0 需要注意,**使用大括号初始化数组时,大括号中不能为空**,至少要写一个值。如`int arr[10] = {};` 语法错误! ### 下标访问 ### 要访问数组中的任意一个元素,都可以通过数组下标访问。因为数组是有顺序的,下标就是元素的序号。但是要注意,数组的第一个元素的序号是0,也就是说下标是从0开始的。 int a[6] = { 12,4,5,6,7,8}; // 打印数字中的元素。使用: 数组变量[下标]的格式获取元素 printf("%d \n",a[0]); printf("%d \n",a[1]); ![在这里插入图片描述][2019060400204653.jpg] **遍历数组** int a[6] = { 12,4,5,6,7,8}; // 使用for 循环来访问数组中的每一个元素 for(int i=0;i<6;i++){ printf("%d \n",a[i]); } // 使用for循环修改数组元素 for(int i=0;i<6;i++){ a[i] = i+2; printf("%d \n",a[i]); } 要注意,在访问数组元素时,\[\]括号中的下标可以是整型变量。 ### 计算数组长度 ### 虽然我们可以明确的知道数组的长度,但有时候我们需要编写更友好更易于维护的代码,例如数组的长度经常修改,则我们需要修改每一处使用数组长度的地方,不易于维护,因此我们需要能动态的计算出数组长度,而不是将长度写死。 前面我们已经多次使用过`sizeof`运算符,该运算符可以获取类型或变量的内存大小,那么我们可以使用它获得数组总内存大小(即数组占用多少内存),然后用总内存大小除以每一个元素占用的内存大小,就可以获得数组的长度了。由于数组存放的都是同一种类型数据,因此每一个元素占用的内存大小都是固定且相等的。 int a[6] = { 12,4,5,6,7,8}; // 计算数组长度。数组总内存大小/每个元素内存大小 int len = sizeof(a)/sizeof(int); for(int i=0;i<len;i++){ printf("%d \n",a[i]); } 如上例,当修改数组大小时,只需要修改数组`a`的声明大小,其他地方不需做任何修改。 ### 数组使用小结 ### 1. 声明数组时,数组长度必须使用常量指定 2. 数组应当先初始化再使用 3. 数组的下标(序号)是从0开始的 4. 访问数组时必须做边界检查。例如数组a的长度为5,则使用`a[5]`访问是错误的。`a[5]`表示的是数组的第6个元素,访问超出数组长度的元素会导致程序异常退出。如果数组长度是`n`,则当`a[i]`访问时,应当保证`i < n` ## 字符与字符串 ## 如果对于字符、字符编码这些不是非常清楚,或者说是一知半解,建议先看看博主的另一篇科普文章,对与字符与字符编码有了更深入的理解再学习以下内容 [**字符编码的前世今生——一文读懂字符编码**][Link 1] ### char 字符 ### C语言中字符是非常简单的,同时也意味着非常原始! // 声明一个字符变量 char s = 'a'; 在C语言中,字符类型的字面量是单引号括起来的一个字符,注意,字符不是字符串,它只能写一个。且`char`类型的字符只能表示`ASCII`表中的字符。实际上,C语言的`char`就是一个整数,它的范围是`0~127` char s = 'a'; char s1 = 97; // 可以看到,s和s1打印的结果完全相同 printf("%c \n",s); printf("%c \n",s1); // 以整数形式打印字符`a` printf("%d \n",s); `char`保存的这个整数也就是每个字符对应的编号,具体的内容我们可以查看`ASCII`表 ![在这里插入图片描述][b13fe00ea6ab5d6e8bce9691492d6c21.png] 仔细观察这张表,我们可以发现一个好玩的规律,所有大写字母的编号都比它对应的小写字母小32。例如`a`的编号是97,则`A`的编号是`97-32=65`。发现这个规律,我们就能非常简单的实现大小写字母的转换了。 char s1 = 'c'; char s2 = 'G'; printf("%c \n", s1-32); //小写转大写 printf("%c \n", s2+32); //大写转小写 打印结果 C g 由于`char`本质上是整数类型,因此可以直接进行算术运算。 ### 宽字符 ### 有些朋友已经发现了,`char`类型是C语言发展的早期,未考虑地区性字符的产物。简单说就是不能表示中文。直接`char s1 = '中';`这样写编译会报错的,后续当然是要出台补救措施,宽字符就是补救措施的产物。需要注意,这里宽字符概念仅作为知识拓展,这种解决方案基本被时代所遗弃,仅部分陈旧项目或某些系统内部编码使用。 #include <stdio.h> // 使用宽字符,需包含头文件 #include <wchar.h> int main(){ // 声明宽字符,字面量前需加上大写L wchar_t s = L'中'; printf("size is %d \n",sizeof(wchar_t)); printf("code = %d \n",s); } 打印结果: size is 2 code = 20013 可以看到,这里宽字符`中`的编号是20013,显然一个字节是存不了这么大的整数的,因此宽字符使用两个字节来存字符的编号。这就是为什么被称为宽字符的原因,它比`char`要宽,使用两个字节16位表示。 在中国大陆区的Window系统中,默认使用的编码表是GBK,并且Windows还使用一种页的概念来表示编码表,而GBK编码表对应的就是`page 936`,也就是第936页表示GBK编码。如要查看GBK编码表,可将`page 936`的内容下载下来查看,[链接地址][Link 2] 复制该连接地址,选择目标另存为即可下载该txt文件 打印输出宽字符,比直接打印`char`要麻烦 #include <stdio.h> #include <wchar.h> // 使用setlocale需包含头文件 #include <locale.h> int main(){ wchar_t s = L'中'; // 需先设置本地的语言环境,第二个参数传"",表示使用本机默认字符集 setlocale(LC_ALL, ""); // 两种打印宽字符的方式,其中wprintf为宽字符专用函数 wprintf(L"%lc \n",s); printf("%lc \n",s); } ### 字符串 (String) ### 所谓字符串,顾名思义,就是将许多单个字符串成一串。既然要把多个字符串起来,当然就需要用到上面说的数组了,存放`char`类型元素的数组,被称为**字符数组**。由于C语言没有专门为字符串提供单独的类型,因此只能使用字符数组的方式来表示字符串,这是与其他编程语言很大不同的地方,也是比较繁琐的地方,如果说其他高级语言是自动挡的小轿车,那么C语言就是手动挡的轿车。 **声明并初始化字符串** //1. 与普通数组相同,用花括号初始化 char str1[30] = { 'h','e','l','l','o','w','o','r','l','d'}; char str2[20] = { "hello world"}; //字符数组的特殊方式 //2. 字符数组特有的方式。使用英文双引号括起来的字符串字面量初始化 char str3[20] = "hello world"; //3. 省略数组长度 char str4[] = { "hello world"}; //4. 省略数组长度,并使用字符串字面量初始化 char str5[] = "hello world"; 在C语言中声明字符串,推荐以上第4种方式,它具有简洁且能避免出错的优点。 #### 字符串与普通数组的区别 #### 在C语言中,虽说字符串是用字符数组来表示的,但是字符串和普通字符数组仍然是不同的,这两者的区别可以简单总结为如下三点 1. C语言字符串规定,结尾必须包含一个特殊字符`'\0'`,我们查询一下`ASCII`表可知,该字符属于控制字符,即无法打印显示出来的字符,它在`ASCII`表中的编号是0,即表中的第一个字符`NUL`。 2. 字符串的实际长度(即字符的个数)比字符数组的长度小1。 3. 声明的同时,数组只能使用花括号初始化,而字符串可以使用双引号括起来的字面量初始化。 现在通过代码验证以上结论 // 请注意,以下代码会造成无法预知的错误。不可为! char s1[3] = { 'a','b','c'}; printf(" %s \n",s1); // 手动添加字符串结束符'\0'或整数0。正确 char s2[4] = { 'a','b','c','\0'}; printf(" %s \n",s2); //只要预留结束符的位置,编译器会自动帮我们添加,无需手动 char s3[4] = { 'a','b','c'}; char s4[4] = "abc"; printf("s3=%s s4=%s \n",s3,s4); 通过以上代码验证,我们就会发现,使用`char str5[] = "hello world";`方式声明并初始化字符串是最好的做法,既简洁,也无需操心是否预留了字符串结束符的位置,因为编译器会自动帮我们计算好。最后再强调一次,由于字符串末尾会自动添加`\0`结束符,因此字符串的实际长度会比字符数组的长度小1。 **声明时不初始化** char str[20]; /* 错误的赋值方式!!! str = "abc"; str = {"abc"}; */ // 不规范的使用方式 str[0]='a'; str[1]='b'; str[2]='c'; printf("%s",str); 以上代码是不规范的使用方式。当我们声明字符数组时未初始化就使用了,则编译器不会自动为我们添加结束符`\0`,使用微软的VC编译器进行编译后,直接出现了乱码情况,虽然GCC不会出乱码,但也存在不可预知的问题。 abc烫烫烫烫烫烫烫烫烫烫特3臋H? 正确的做法是在未初始化的情况下,使用字符串数组应手动添加结束符 char str[20]; str[0]='a'; str[1]='b'; str[2]='c'; str[3]='\0'; printf("%s\n",str); 当然,除了手动添加结束符号,还可以使用C语言标准库的函数来自动初始化数组。这是一种更常用的做法 #include <stdio.h> #include <string.h> // 需要包含string.h头文件 int main(){ char str[20]; // 将数组初始化化为指定的值,这里指定0,第三个参数是数组的内存大小 memset(str, 0, sizeof(str)); str[0] = 'a'; str[1] = 'b'; str[2] = 'c'; printf("%s", str); return 0; } ### 小拓展: ### **使用VC编译器,未初始化的数组为什么会出现“烫烫烫”**? 因为VC编译器默认会干一件事情,将未初始化的字符数组,使用十六进制数`0xcc`进行填充 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA_size_16_color_FFFFFF_t_70] 观察以上内存布局图,可知前三个元素分别是十六进制`0x61`、`0x62`、`0x63`,转换成十进制就是97、98、99,正好是a、b、c的ASCII码编号,剩余数组元素则默认都是`0xcc`,而它的十进制则是204,显然已经超出了ASCII码表的范围,Windows默认使用GBK码表,用两个字节表示一个汉字。这时候我们去查询`page 936`表,可发现两个`cc`字节合起来就是汉字**烫** ![在这里插入图片描述][2019060818310398.jpg] 还可以查GBK的表,首字节`cc`的平面表如下,然后根据尾字节去查具体对应的汉字,这里尾字节也是`cc` ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA_size_16_color_FFFFFF_t_70 1] 除了被填充成`cc`,乱码还与数组越界有关。因为没有字符串结束符`\0`,使用printf打印的时候,它并不知道应该在哪儿结束,因为内存都是连成一片的,超过`str[20]`的20个元素范围,后面还有内存空间,因此乱码 `abc烫烫烫烫烫烫烫烫烫烫特3臋H?`明显超出了20个char的范围,将其他的内存内容也打印了。这就好比你家住18号,你不仅把18号的门打开了,还把隔壁19号的门也撬开了。 ### 字符串的常用函数 ### C语言虽然是手动挡的,但也为我们提供了一些不太完美的标准库函数,虽然这些函数多多少少都存在一些坑,但也聊胜于无,总比我们事事躬亲要强许多。要想使用字符串库函数,需要包含`string.h`头文件。 #### 字符串长度 #### * strlen #include <stdio.h> #include <string.h> int main(void){ char str[]= "hello world!"; // 动态计算str数组的长度 printf("array size is %d\n",sizeof(str)/sizeof(char)); // 获取字符串的长度 int len = strlen(str); printf("string size is %d\n",len); return 0; } 打印结果: array size is 13 string size is 12 可见`str`数组共用13个元素,但只有12个有效字符,最后一个为`\0`结束符 #### 比较字符串内容 #### 当我们要判断两个字符串是否相同时,是不能直接使用比较运算符`==`操作的 char str1[]= "hello"; char str2[]= "hello"; // ==比较的是两个数组的地址,而不是内容,结果与预期不符 printf("%d\n",str1 == str2); * strcmp #include <stdio.h> #include <string.h> int main(void){ char str1[]= "hello"; char str2[]= "hello"; // strcmp的返回值等于0时,表示两个字符串内容相同,否则不同 if (strcmp(str1,str2) == 0){ printf("str1 == str2\n"); }else{ printf("str1 != str2\n"); } char str3[]= "bruce"; char str4[]= "hello"; if (strcmp(str3,str4) == 0){ printf("str1 == str2\n"); }else{ printf("str1 != str2\n"); } return 0; } 打印结果: str1 == str2 str3 != str4 #### 字符串的复制 #### * strncpy 还可使用该函数为字符数组进行初始化 #include <stdio.h> #include <string.h> int main(void){ char str1[100]={ 0}; // 将字符串复制到指定的字符数组中,并自动复制结束符。第一个参数就是目的地 // 第三个参数需指定复制的长度,这里指定目标数组的大小,表示如果超过这个长度则以这个长度为止 strncpy(str1,"Greetings from C",sizeof(str1)); printf("str1=%s\n",str1); // 将str1的内容复制到str2中 char str2[50]={ 0}; strncpy(str2,str1,sizeof(str2)); printf("str2=%s\n",str2); return 0; } **暗坑** `strncpy`函数存在一个问题,如果被复制的字符串长度太长,超过了目的数组的长度,则将目的数组填充满为止,但是这种情况下就不会添加`\0`结束符,导致存在不可预知的问题。 #include <stdio.h> #include <string.h> int main(void){ char str1[10]={ 0}; // 字符串超过str1的长度,导致str1没有结束符 strncpy(str1,"Greetings from C", sizeof(str1)); printf("str1=%s\n",str1); // 乱码 char str2[10]={ 0}; // 更安全合理的做法,始终为结束符预留一个位置 strncpy(str2,"Greetings from C", sizeof(str2)-1); printf("str2=%s\n",str2); // 字符串虽被截断,但是有结束符,安全! return 0; } #### 字符串的拼接 #### 在其他语言中,通常只需要简单的使用`+`号就能拼接字符串,但是C语言就显得繁琐 * strncat #include <stdio.h> #include <string.h> int main(void){ char str1[100] = "hello"; // 将第二个参数的内容追加到第一个参数的后面,相当于将两者拼接 // 第三个参数为拷贝的长度,类似strncpy, // 这里计算数组的总长度减去字符串的长度,求得str1剩余空间的长度 strncat(str1," world!",sizeof(str1)/sizeof(char)-strlen(str1)); printf("str1=%s\n",str1); return 0; } 同`strncpy`函数相似,这里的暗坑也是目的地数组的空间不足导致丢失结束符的问题,因此应当预留结束符的位置 strncat(str1," world!",sizeof(str1)/sizeof(char)-strlen(str1) - 1); # 欢迎关注我的公众号:编程之路从0到1 # ![编程之路从0到1][0_1] [2019060400204653.jpg]: /images/20220123/f0de1763a9ab480c928bc640078e80c1.png [Link 1]: https://arcticfox.blog.csdn.net/article/details/91347405 [b13fe00ea6ab5d6e8bce9691492d6c21.png]: /images/20220123/a40217edc71d4f8682d190eeb29892df.png [Link 2]: https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit936.txt [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA_size_16_color_FFFFFF_t_70]: /images/20220123/da4140d34fdb4a948859e33dc800a95d.png [2019060818310398.jpg]: /images/20220123/875a7746e2f54b47a4793c07073e3405.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9hcmN0aWNmb3guYmxvZy5jc2RuLm5ldA_size_16_color_FFFFFF_t_70 1]: /images/20220123/2b4dafac44564267a9c69f537d3e2834.png [0_1]: /images/20220123/1b39201f22ae46408d3aaf5de143f536.png
相关 程序员C语言快速上手——基础篇(三) 文章目录 小拓展:C语言中int的正确使用姿势 语法基础 表达式 算术运算符 关系运算符 待我称王封你为后i/ 2022年01月23日 00:19/ 0 赞/ 283 阅读
相关 程序员C语言快速上手——高级篇(九) 文章目录 高级篇 结构体 背景 结构体的声明与使用 结构体变量的初始化 太过爱你忘了你带给我的痛/ 2021年12月09日 20:55/ 0 赞/ 352 阅读
还没有评论,来说两句吧...