前言
学习方法
- 可以多看几遍视频
- 把上课的代码,自己加加注释,在自己写之前,可以画一个流程图
- 照着流程图把代码自己实现一遍
不要怀疑自己,不要遇到困难就觉得自己不行,遇到困难就解决困难,编程初学者都是这样一步一步走过来的。
在指针阶段会演示整型、浮点型、字符型传递(传递的含义就是把一个变量传递给对应的子函数。)
一维数组
数组的定义
所谓数组,是指一组具有相同数据类型的数据的有序集合。
数组具有以下特点:
- 具有相同的数据类型
- 使用过程中需要保留原始数据
一维数组的定义格式为:
类型说明符 数组名 [常量表达式]
例如,定义一个整型数组,数组名为a,它有10个元素。
int a[10];
声明数组时要遵循以下规则:
- 数组名的命名规范和变量名的相同,即遵循标识符命名规则。
- 在定义数组时,需要指定数组中元素的个数,方括号中的常量表达式用来表示元素的个数,即数组长度。
- 常量表达式中可以包含常量和符号常量,但不能包含变量。也就是说,C语言不允许对数组的大小做动态定义,即数组的大小不依赖于程序运行过程中变量的值。
int main() {
//定义数组就是写一个变量名,后面加上方括号,方括号内写上整型常量
//定义数组时,数组占据的空间大小就确定下来了
int a[5] = { 1,2,3,4,5 };
}
使用符号常量,不需要掌握,知道可以这样写即可。
//符号常量
#define N 5
int main() {
int a[N] = { 1,2,3,4,5 };
}
查看数组内存空间
看任何变量的内存,都是将该变量取地址,拖入内存窗口来看。 只有指针类型才能拖入内存窗口查看。带 * 号的表示是指针类型。
赋初值
int a[5] = {1,2,3};
定义数组a有5个元素,但花括号内只提供3个初值,这表示只给前3个元素赋初值,后2个元素的值为0。
如果要使一个数组中全部元素的值为0,那么可以写为
int a[5] = {0,0,0,0,0};
或者
int a[5] = {0};
但肯定是第二种写法为好(当赋初值的个数小于数组大小时,则后面的元素会被自动赋值为0)。
数组访问越界
了解一下:微软的编译器设计,不同的变量之间有8个字节的保护空间(Mac和Linux是没有的)。[这并不是标准C规定的]。
数组访问越界的错误提醒:
Run-Time Check Failure #2 - Stack around the variable ‘a’ was corrupted.
原理:
由上面的例子可知,访问越界会造成数据异常。
简单了解说明一下为什么C的执行效率比Java高:C语言中有指针,可以操作内存,因此效率高;而Java中没有指针,内存交给编译器管理(操作系统管理),提高了开发效率,但执行效率降低。
数组传递
自定义的函数必须写在main函数的上面。
注意,自己写的其他方法要放在main方法的上面,否则编译不通。
实现打印数组里的每一个元素,这里先将 “打印数组元素的方法” 中数组的大小写成固定数值。
//打印数组里的每一个元素
//自己写的print方法必须放在main方法的上面,否则编译不通
void print(int a[5]) {
for (int i = 0; i < 5; i++)
{
printf("a[%d]=%d\n", i, a[i]);
}
}
int main() {
int a[5] = {1,2,3,4,5};
print(a);
}
得到输出结果:
现在想在 “打印数组元素的方法” 中动态的得到数组的大小,进行如下尝试:
//打印数组里的每一个元素
void print(int a[5]) {
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
printf("a[%d]=%d\n", i, a[i]);
}
}
int main() {
int a[5] = {1,2,3,4,5};
print(a);
}
而这次的输出结果是:
通过断点调试发现sizeof(a)=4,而与数组a的大小无关。无论传过来的数组多大,sizeof(a)始终等于4。原因在于数组在传递的时候数组元素是传递不过去的,只能传递数组的起始地址(实际上就是指针)。
正确的写法是这样的,在print方法中新增一个参数表示数组的大小,调用该方法时传入数组的大小。[注:C语言中没有Java中的a.length方法]。
//打印数组里的每一个元素
void print(int a[],int len) {
for (int i = 0; i < len; i++)
{
printf("a[%d]=%d\n", i, a[i]);
}
}
int main() {
int a[5] = {1,2,3,4,5};
print(a,5);
}
//以下代码实现也是正确的
/*
//形参和实参可以不一样
void print(int b[],int len) { //b、len是形参
for (int i = 0; i < len; i++)
{
printf("a[%d]=%d\n", i, b[i]);
}
}
int main() {
int a[5] = {1,2,3,4,5};
print(a,5);//a是实参
}
*/
在子函数中修改数组元素
在子函数中可以直接修改数组元素,可以读可以写。
//打印数组里的每一个元素
//print是我们自定义的函数,C语言中没有print函数
void print(int b[],int len) {
for (int i = 0; i < len; i++)
{
printf("a[%d]=%d\n", i, b[i]);
}
b[4] = 20;//在子函数中修改数组元素
}
int main() {
int a[5] = {1,2,3,4,5};
print(a,5);//调用函数
printf("a[4]=%d\n", a[4]);
}
输出结果:
字符数组
初始化字符数组
字符数组就是专门用来存字符串的(初试和机试)。
初始化字符数组:以后都用第二种初始化字符数组的方式char d[10] = “hello”;
int main() {
char c[10] = {'h','e','l','l','o'};//这样初始化字符数组太麻烦了
char d[10] = "hello";//这样初始化字符数组也可以,而且简单
//给字符数组存一个字符串,就用第二种初始化方式,简单。
//字符数组就是专门用来存字符串的(初试和机试)
}
C语言规定字符串的结束标志为’\0’,读到\0就不会再输出了,而系统会对字符串常量自动加一个’\0’,为了保证处理方法一致,一般会认为地在字符数组中添加’\0’,所以字符数组存储的字符串长度必须比字符数组少1字节。例如char c[10]最长存储9个字符,剩余的1个字符用来存储’\0’。
ASCII码表中\0对应编码是0000 0000 即值是0,\0在内存中存的值就是0.
所以正确的初始化字符数组代码如下:初始化字符数组时,一定要让字符数组的大小比看到的字符串的长度多1。
//初始化字符数组时,一定要让字符数组的大小比看到的字符串的长度多1
int main() {
char c[6] = {'h','e','l','l','o'};//字符数组存储的字符串长度必须比字符数组少1字节
char d[5] = "how";
//%s :输出一串字符串
printf("%s------%s", c, d);//printf的%s,对应后面要写的是字符数组名或者字符串常量
//printf("%s", "Chinese");//printf的%s,后面写字符串常量的例子
}
向字符数组中读取字符串
scanf中%s不用加&取地址符。[原因在于数组名本身存储的就是数组的起始地址(指针)。当然这里写上&取地址符也不会错,但一般不写].
%s和%d,%f一样都会忽略空格和\n。
scanf读取我们输入的字符串后会自动添加结束符\0。
//向字符数组中读取字符串
int main() {
char e[20];
//scanf中%s不用加&取地址符
scanf("%s", e);//%s和%d,%f一样都会忽略空格和\n。
printf("%s\n", e);
}
同时读取多个:
int main() {
char e[20],f[20];
scanf("%s%s", e,f);//,当读取多个时%s%s中间不用加空格,因为%s和%d,%f一样都会忽略空格和\n。
printf("%s---%s\n", e,f);
}
%s、%d、%f都会忽略空格和\n,因此在输入多个时中间不用加空格,而%c不会忽略空格和\n,所以在多个混合输入时有%c出现时要在 %c前面加一个空格。
如何把wangdao xue408 读到同一个字符数组中(包括那个空格),用scanf实现较为麻烦,因为需要用到正则,不需要掌握。用gets就很容易实现了,gets可以一次读一行,中间有空格也可以读到一个字符数组中。
字符数组的传递
自定义的函数必须写在main函数的上面。
/*
在传递整型数组时不光要传递数组名,同时还要传递数组长度。而字符数组在传递时,只需传递数组名即可,
不用传递数组长度。因为字符数组最后一个是 '\0',C语言规定字符串的结束标志为'\0',所以当扫描到'\0'时
就知道应该结束了,可以以此来作为循环结束的判断条件。
*/
//我们把d称为形参
void print(char d[]) {
int i = 0;
/*
由于\0在ASCII码表中对应的编码就是0,所以循环判断条件也可以简写为while (d[i]!=0);
又C语言中一切非0值都是真,0为假,所以可以再进一步简写为while (d[i]);
*/
while (d[i]!='0')
{
printf("%c", d[i]);
i++;
}
printf("\n");
}
int main() {
//初始化字符数组时,一定要让字符数组的大小比看到的字符串的长度多1
char c[10] = "hello";
print(c);//我们把c称为实参,调用print函数时,是d=c (本质上就是赋值)
}
同整型数组一样,同样可以在子函数中修改字符数组元素。如下:
void print(char d[]) {
int i = 0;
while (d[i]!='\0')
{
printf("%c", d[i]);
i++;
}
printf("\n");
//在子函数中修改字符数组元素。修改字符数组中的字符串的内容,把首字母变大写。
d[0] = d[0] - 32;//A:65 a:97 大写字母与小写字母的差是32
}
int main() {
//初始化字符数组时,一定要让字符数组的大小比看到的字符串的长度多1
char c[10] = "hello";
print(c);
printf("%s\n", c);
}
gets函数
gets函数只能读取字符串。
使用scanf读取字符串会遇到一个问题,scanf通过%s读取字符串时,当遇到空格以后,就会匹配结束,这样没办法把一行带有空格的字符串存入到一个字符数组中。这个问题可以用gets解决。
gets函数的格式如下:
//char *str类型是字符指针
char *gets(char *str);
gets函数从标准输入中读取字符并把它们加载到str(字符串)中,直到遇到换行符(\n)或到达EOF。【所以gets可以一次读一行,空格也能读入】
输入“how are you”,共11个字符,可以看到gets会读取空格,同时可以看到我们并未给数组进行初始化赋值,但是最后有’\0’(因为字符串的结束标志是\0,只有遇到\0才会停止输出),这是因为gets遇到\n后不会存储\n,而是将其翻译为空字符’\0’。
gets读一个字符就将其填到字符数组中,当读到\n时不会将其填到字符数组中,当读到\n就会停止匹配,同时gets会给字符数组填结束符\0,所以可以理解为将\n翻译为了\0。
字符数组的数组名存的就是字符数组的起始地址,类型就是字符指针。而数组的起始地址就是第一个元素的地址,即有&c[0]==c。如下图:
好好理解下面这段话:
char c[20]; c是一个字符数组,但是编译器给c内部存了一个值,c里面存的值的类型是字符指针。
我们在定义其他变量时如整型变量i、浮点型变量f、字符型变量,我们拿这些变量的值时直接拿这个变量名即可,如直接访问i。但在定义数组时如char c[20],拿数组里的每一个元素时不会再去访问数组名c,而只会去访问c[0]-c[19],这时候编译器就会把数组名c中填一个固定的值。
int main() {
char c[20];//字符数组的数组名里存的就是字符数组的起始地址,类型就是字符指针
gets(c);
/*
函数调用是值传递,是把c里存的值传给gets,c里的值类型是字符指针,符合gets函数的格式
*/
puts(c);
}
puts函数
puts只能输出字符串,printf可以输出所有类型如整型、浮点型、字符型、字符串等。
puts函数的格式:
int puts(char *str);
str系列字符串操作函数
初试中如果不考字符串的操作,则一般用不到str函数;但如果初试中考了字符串的操作,则用到str函数的概率非常高。需要指出的是过去只有一年在初试科目中考察了字符串处理。但str函数在机试中很重要。
str系列字符串操作函数主要包括strlen、strcpy、strcmp、strcat等(可以查询C/C++函数大全)。strlen函数用于统计字符串长度,strcpy函数用于将某个字符串复制到字符数组中,strcmp函数用于比较两个字符串的大小【两个字符串比较,是比较对应字符位置的ASCII码。(从前往后依次逐个对比字符的ASCII码值)。】;strcat函数用于将两个字符串连接到一起。各个函数的具体格式如下所示:[注:当有const修饰表示此处可放字符串常量]
#include <string.h>
size_t strlen(char *str);
char *strcpy(char *to,const char *from);
int strcmp(const char *str1,const char *str2);
char *strcat(char *str1,const char *str2);
size_t就是int类型,当不知道是什么类型时可以用如下操作:
对于传参类型char*,直接放入字符数组的数组名即可。
注意使用str系列函数需要使用#include <string.h>头文件
//strlen函数是得到字符数组里字符串的长度。
int main() {
int a,b;
char c[20] = "wangdao";
a = sizeof(c);//使用这种方法得到的是字符数组的长度,输出结果a=20。
printf("%d\n", a);
b = strlen(c);//使用这种方法得到的是字符数组里面字符串的长度,输出结果b=7。
printf("%d\n", b);
}
练习使用str系列函数:
int main() {
/*
strlen函数是得到字符数组里字符串的长度,同时字符数组里的结束符\0不计入总长度内
*/
char c[20] = "wangdao";
printf("数组c内字符串的长度=%d\n", strlen(c));
char d[20];
strcpy(d, c);//strcpy是把一个字符串复制给另一个字符串,d赋给c
/*
strcpy的格式如下:char *strcpy(char *to,const char *from);
当有const修饰表示此处可放字符串常量
如放置字符串常量的例子:
strcpy(d, "study");
*/
puts(d);
/*
strcmp:比较两个字符串的大小。
格式:int strcmp(const char *str1,const char *str2);两个地方都有const修饰说明两个地方都能放字符串常量
str1 > str2 返回值为1;str1 < str2 返回值为-1;str1 = str2 返回值为0
两个字符串比较,是比较对应字符位置的ASCII码。(从前往后依次逐个对比字符的ASCII码值)。
如比较"how"与"hello",先对比第一个字符,相等;再对比第二个字符,o的ASCII码大于e的ASCII码,所以how>hello
*/
int ret = strcmp("how", "hello");
printf("两个字符串比较后的结果=%d\n",ret);
/*
strcat:拼接两个字符串。格式char *strcat(char *str1,const char *str2);
strcat实现拼接的原理是将后面的填入前面的字符数组中。因此目标数组要能够容纳拼接后的字符串(还应包括一个结束符)
*/
strcat(c, "study");
puts(c);
}
输出结果: