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

------Java培训、Android培训、iOS培训、.Net培训 期待与您交流!-------

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

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


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

先来看看代码头

#include 
#include 
#define M 100
#define N 4

include是包含程序中需要用到的函数的头文件
关于define这是预处理指令中的宏定义

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

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

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

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

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

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

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

1.数组

数组的存储实际上就是数据结构中所说的线性表,也就是从首地址开始,每个数组元素依次向后存储。
数组的声明:类型 数组名[下标];
比如上述的int count[N] = {0};
关于数组,需要注意的有以下几个点:
(1)数组名表示数组首元素的地址(这在把数组当做参数传给函数时要注意,上述代码中的Sort(count),就是这种用法)。
(2) 的初始化只能在定 候,上述代码中的int count[N] = {0};就是如此,如果把这段代码拆开编程下面的代码
 int count[N];
count[N]={0};
这样是错误的,编译的时候就会报错,一定要注意了。
(3) 的位置只能用常量而不能用 量,大家一定注意到这段代码了int count[N] = {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[N];
    //对结构体变量进行赋值
    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[N];这一句是利用上面的结构体声明创建一个用于输出的结构体数组,然后利用for循环语句对其进行赋值。
最后的冒泡排序是利用了双重嵌套循环,从首元素开始,将它后面所有的数据与它进行比较,如果后面的数据比当前的数据大,那么两个元素进行交换。
最后,将排序后的数组打印。
这里有一点我自己的习惯,重新定义一个用于输出的数组是为了保障输入的值不变,也就是数据源不会改变,当然,输入和输出用同一个数组在实现上是完全没有问题的。
 




你可能感兴趣的:(黑马程序员——C语言基础篇---宏定义、数组、字符串和函数)