您的位置:首页 > 编程语言 > C语言/C++

黑马程序员——C语言基础篇---宏定义、数组、字符串和函数

2015-04-14 16:35 295 查看
------Java培训、Android培训、iOS培训、.Net培训 期待与您交流!-------

本篇将要通过一道简单的C语言小程序题来引入今天的主题:宏定义、数组、字符串和函数。下面先来看看我们的题目吧

* 从键盘输入一大堆字符串,统计A、B、C、D的出现次数,最后出现次数由高到低输出字母和出现次数。

这道题在我看来可以分为三大功能模块:输入字符串、统计次数、排序并输出,这就决定了我们主函数的结构

先来看看代码头

<strong>#include <stdio.h>
#include <stdlib.h>
#define M 100
#define N 4</strong>


include是包含程序中需要用到的函数的头文件

关于define这是预处理指令中的宏定义

本题使用的是它不带参数的定义

#define 宏名 字符串(注意:结尾不用【;】)

也就是在程序中我们使用宏名来表示右面的字符串内容,宏定义其实就是在程序运行前编译的时候,将宏名替换成右边的字符串,再进行后续操作,个人感觉这是最常见的一种使用方式,说到这里再介绍另外一种宏定义的使用方法:带参数的宏定义

下面的代码与题目无关,只是作为一个宏定义使用方法的补充

#include <stdio.h>
#define TEST(a) a * a
int main()
{
int r = TEST(2+2);
printf("%d\n",r);
}


结果是4

但是这个宏定义是有问题的,如果变成 TEST(2+2),结果却不是16,而是8,这是因为宏定义是是纯粹的替换,而不能像函数一样能帮你做计算,TEST(2+2)被替换成2+2 * 2+2,结果自然是8,所以在定义时需要给变量套上 (),但这样也还是不够的,还要给结果也加上()

改进后的代码 (下面的代码与题目无关)

#include <stdio.h>
#define TEST(a) ((a) * (a))
int main()
{
int r = TEST(5+5)/TEST(2+3);//这样可以测出宏定义结果的正确性
printf("%d\n",r);
}
回到我们的题目,下面是main函数中的代码,对应上面所述,分成了三大模块

int main()
{
int count
= {0};
//定义接收从屏幕输入的字符串变量,并分配存储空间
char *input = (char *) malloc(M);
printf("请输入字符串:\n");
//gets()函数会报warning,但是这里要接收空格,所以还是使用了
gets(input);
//调用统计函数
Statistics(input,count);
//调用排序函数
Sort(count);
return 0;
}


从这段代码中就可以找到我们今天的主题:数组、字符串和函数,下面我们分开来简单讲讲他们的用法。

1.数组

数组的存储实际上就是数据结构中所说的线性表,也就是从首地址开始,每个数组元素依次向后存储。
数组的声明:类型 数组名[下标];
比如上述的int count
= {0};
关于数组,需要注意的有以下几个点:
(1)数组名表示数组首元素的地址(这在把数组当做参数传给函数时要注意,上述代码中的Sort(count),就是这种用法)。
(2)数组的初始化只能在定义数组的时候,上述代码中的int
count
= {0};就是如此,如果把这段代码拆开编程下面的代码
int count
;
count
={0};
这样是错误的,编译的时候就会报错,一定要注意了。
(3)下标的位置只能用常量而不能用变量,大家一定注意到这段代码了int
count
= {0}; ,这不是用了变量N吗?注意:这是上面的宏定义,值是4,而非变量,如果定义一个变量作为下标使用,编译的时候也是会报错的,但宏定义没问题,它在程序运行时已经是个常量了。
(4)C语言中数组存储的只能是统一类型的数据

2.字符串

字符串,顾名思义“一串”字符,是字符流,以'\0'结尾,'\0'的作用就是用来标识字符串结束了,这点在以%s输出字符串的时候很有用。
下面的代码与题目无关

int main()
{
char *input = "";
input = "hello world";
printf("%s\n",input);
return 0;
}


字符串的定义,必须是 char * ,实际上就是个指针,而非字符数组,比字符数组多了【'\0'】,在以%s输出字符串的时候,遇到‘\0’就停止输出,如果定义字符数组

char input[100] = {}; ,由于没有‘\0’,会从起始地址开始一个一个输出字符,直到遇见'\0'为止。但如果这么写,char
input[100] = {‘\0’},这样没问题了。

本段代码中,这一句代码 char *input = (char *) malloc(M); 这是因为如果定义一个字符串变量来接收从屏幕输入的字符串,它此时还没有分配存储空间,无法使用,所以通过这句给它分配存储空间,这样后面的使用就完全没有问题了,因为malloc这个函数,包含了stdlib.h。

还有一点需要注意的:

计算字符串长度(指的是字节数,而非字数,比如1个汉字占3个字节,它的长度是3而不是1)

sizeof() 输出字符串内所有的字节数,包括'\0'

strlen() 输出字符串内所有的字符数,不包括'\0'

3.函数

函数其实就是将一段功能代码封装,需要使用的时候传入(或不传)参数调用即可
函数在使用前必须有其声明,习惯上会写到主函数前面,如果整个函数都在主函数前面就不需要单独声明了
//统计函数的声明
void Statistics(char *in,int c[]);
//排序函数声明
void Sort(int c[]);

默认情况下,是外部函数(extern),也就是其他文件也可以使用,如果限制于本文件使用需要在void前面加上关键字 static

void 表示无返回值,如果有返回值的话需要更改成int 、double等需要的类型
()内表示需要传入的参数,是实参,局部变量
下面的代码与题目无关

void test(int a)
{
}
传入的参数是基本数据类型,这是进行了值传递,既只是把原来变量的值传递给实参,改变这个值,原来变量的值并不会被修改。
还有一种是传地址,也就是我们练习中的代码
//统计函数:从输入的字符串中,统计A、B、C、D的出现次数
void Statistics(char *in,int c[])
{
//遍历字符串
while (*in != '\0')
{
//统计A、B、C、D的出现次数
if (*in >= 'A' && *in <= 'D')
{
c[*in - 'A']++;
}
in++;
}
}


这里的两个参数都是进行的地址传递,也就是将主函数中的字符串和数组的地址当做形参传了进来,所以在函数中修改其值是可以的。
这段小代码中用到了while(循环结束的条件)循环结构和if分支结构,在统计次数时有一个小的技巧,那就是数组的下标使用了 *in - 'A' ,这样不必用分支语句去判断再统计了,c[0]表示A的次数,c[1]表示B的次数,c[2]表示C的次数,c[3]表示D的次数,c指向count,所以此时count里的值也是对应的。这里对输入字符串的遍历是通过指针,从首地址开始,利用
*in 访问每一个字符,in++表示指针移动到下一个字符。
在这里有一点需要注意,本段代码中的c 如果利用sizeof来计算的话,与我们以前在主函数中调用结果不同,得到的是8字节,因为这里已经变成指针了,所以要使用sizeof计算数组长度的话,最好是在主函数中进行。
//排序函数:由高到低排序,并输出
void Sort(int c[])
{
//定义排序的结构体类型
struct NewSort
{
char c;
int times;
};
//定义结构体变量
struct NewSort out
;
//对结构体变量进行赋值
for (int i = 0; i < N; i++)
{
out[i].c = 'A' + i;
out[i].times = c[i];
}
//由大到小排序(冒泡排序)
for (int i = 0; i < N; i++)
{
for (int j = i + 1; j < N; j++)
{
if (out[j].times > out[i].times)
{
struct NewSort t;
t = out[j];
out[j] = out[i];
out[i] = t;
}
}
}
//输出排序后的字母及其出现次数
for (int i = 0; i < N; i++) {
printf("%c\t%d\n",out[i].c,out[i].times);
}

}


这是最后一段代码了,里面有用到结构体,结构体实际上就是将不同类型的数据集合成一个复杂数据类型,里面可以是基本数据类型、指针、也可以嵌套结构体。
对于结构体所占据的存储空间,采用补齐算法(对齐算法),也就是结构体占据的存储空间是其定义中最大变量长度的倍数。比如上面代码所定义的结构体NewSort,本来它的长度应该是:1(char)+
4(int)=5字节,但是由于补齐算法,它会补成int型字节的倍数,也就是8字节。
struct NewSort out
;这一句是利用上面的结构体声明创建一个用于输出的结构体数组,然后利用for循环语句对其进行赋值。

最后的冒泡排序是利用了双重嵌套循环,从首元素开始,将它后面所有的数据与它进行比较,如果后面的数据比当前的数据大,那么两个元素进行交换。
最后,将排序后的数组打印。
这里有一点我自己的习惯,重新定义一个用于输出的数组是为了保障输入的值不变,也就是数据源不会改变,当然,输入和输出用同一个数组在实现上是完全没有问题的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐