程序员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]);

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

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

字符与字符串

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

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
在这里插入图片描述
仔细观察这张表,我们可以发现一个好玩的规律,所有大写字母的编号都比它对应的小写字母小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的内容下载下来查看,链接地址 复制该连接地址,选择目标另存为即可下载该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进行填充
在这里插入图片描述
观察以上内存布局图,可知前三个元素分别是十六进制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
#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];
    
    // 将字符串复制到指定的字符数组中,并自动复制结束符。第一个参数就是目的地
    // 第三个参数需指定复制的长度,这里指定目标数组的大小,表示如果超过这个长度则以这个长度为止
    strncpy(str1,"Greetings from C",sizeof(str1));
    printf("str1=%s\n",str1);

    // 将str1的内容复制到str2中
    char str2[50];
    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];
    
    // 字符串超过str1的长度,导致str1没有结束符
    strncpy(str1,"Greetings from C", sizeof(str1));
    printf("str1=%s\n",str1);   // 乱码

    char str2[10];
    
    // 更安全合理的做法,始终为结束符预留一个位置
    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

猜你喜欢

转载自blog.csdn.net/yingshukun/article/details/90761095