C语言复习笔记 5

数组(Array)

数组是按序排列的同类数据元素的集合。

数值数组、字符数组、指针数组、结构数组等

1、数组定义方式:dataType arrayName[length]

  • 数组长度length最好是整数或常量表达式
  • 访问数组元素,下标index满足0<=index<length过大过小都会越界,导致数组溢出
  • 数组是一个整体,它的内存是连续的。

在C语言中,数组一旦被定义后,占用的内存空间就是固定的,不能在任何位置插入元素,也不能在任何位置删除元素(当然可以修改元素),我们将这样的数组称为静态数组(Static Array)

访问时越界
赋值时溢出

字符串的长度大于数组长度,数组只能容纳字符串的前面一部分,也就是 “http://c.b“,即使编译器在最后添加了 ‘\0’,它也保存不到数组里面,所以 printf() 扫描数组时不会遇到结束符 ‘\0’,只能继续向后扫描。而后面内存中的数据我们不知道是什么,字符能否识别,何时遇到 ‘\0’,这些都是不确定的。当字符无法识别时,就会出现乱码,显示奇怪的字符。

2、数组的初始化:
int a[4] = {1,2,3,4};注意用{}括号

  • 当赋值元素少于数组总体元素时,剩余的元素自动初始化为0(‘\0’,0.0)
  • 给出全部元素时,可以不写length,int a[] = {1,2,3,4};

二维数组
dataType arrayName[length1][length2]

二维数组在概念上是二维的,但在内存中地址是连续的,也就是说每个元素都是互相挨着的,那在线性内存中是如何存放二维数组的呢,在C语言中二维数组是按行排列的

当有嵌套循环时,尽量内循环次数大于外循环次数

int a[5][3];
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 5; j++) {
        scanf("%d", &a[j][i]);
    }
}

二维数组的初始化

  • 按行分段赋值int a[3][2] = {{1,2},{3,4},{5,6}};
  • 按行连续赋值 int a[3][2] = {1,2,3,4,5,6};
  • 可以只对部分元素赋值,未赋值元素初始化为0;int a[3][2] = {{1},{3},{5}};
  • 如对全部元素赋值,则第一个长度可以 不给出;

二维数组可以看作是由一维数组嵌套而成的,把一维数组的每个元素看作一个数组,就组成了二维数组。当然,前提是各元素类型必须相同。根据这样的分析,一个二维数组也可以分解为多个一维数组,C语言允许这种分解。

如二维数组a[3][4],可分解为三个一维数组,其数组名分别为:a[0]、a[1]、a[2]。

对这三个一维数组不需另作说明即可使用。这三个一维数组都有4个元素,例如:一维数组a[0]的元素为a[0][0], a[0][1], a[0][2], a[0][3]。

必须强调的是,a[0], a[1], a[2]不能当作下标变量使用,它们是数组名,不是一个单纯的下标变量。

3、C语言字符数组和字符串

用来存放字符的数组称为字符数组char a[4] = {'h','e','r','o'};

字符数组实际上是一系列字符的集合,也就是字符串(String)。在C语言中,没有专门的字符串变量,没有string类型,通常就用一个字符数组来存放一个字符串。

C语言规定,可以将字符串直接赋值给字符数组,例如:

char str[30] = {"c.biancheng.net"};
char str[30] = "c.biancheng.net";  //这种形式更加简洁,实际开发中常用

在C语言中,字符串总是以’\0’作为串的结束符。上面的两个字符串,编译器已经在末尾自动添加了’\0’。

‘\0’是ASCII码表中的第0个字符,用NUL表示,称为空字符。该字符既不能显示,也不是控制字符,输出该字符不会有任何效果,它在C语言中仅作为字符串的结束标志。

需要注意的是,用字符串给字符数组赋值时由于要添加结束符 ‘\0’,数组的长度要比字符串的长度(字符串长度不包括 ‘\0’)大1。

4、C语言字符串处理函数

C语言提供了丰富的字符串处理函数,例如字符串的输入、输出、合并、修改、比较、转换、复制、搜索等,使用这些现成的函数可大大减轻编程的负担。

1)、输入输出:用于输入输出的字符串函数,例如printf、puts、scanf、gets等,使用时应包含头文件stdio.h,使用其它字符串函数则应包含头文件string.h。
字符串的输出

在C语言中,输出字符串的函数有两个:
puts():直接输出字符串,并且只能输出字符串。
printf():通过格式控制符 %s 输出字符串。除了字符串,printf() 还能输出其他类型的数据。

字符串的输入

在C语言中,输入字符串的函数有两个:
scanf():通过格式控制符 %s 输入字符串。除了字符串,scanf() 还能输入其他类型的数据。

  • 需要注意的是:scanf() 读取到空格时就认为字符串输入结束了,不会继续读取了——针对字符串。
  • scanf("a=%d",&a);scanf("b=%d",&b);这种情况下输入100回车↙后,回车换行符也会被保存到缓冲区。也就是说当控制字符串不是以 %xxx 开头时(像”a=%xxx”),回车换行符就起作用了,scanf() 会对它进行匹配,只是匹配失败而已。

gets():直接输入字符串,并且只能输入字符串。
可以发现,当输入的字符串中含有空格时,输出仍为全部字符串,这说明 gets() 函数不会把空格作为输入结束的标志,而只把回车换行作为输入结束的标志,这与 scanf() 函数是不同的。


为什么要引入缓冲区,减少磁盘的读写次数
在缓冲区读写速度比磁盘快,效率更高
缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

2)、字符串长度
字符串长度函数strlen:strlen 是 string length 的缩写,用来获得字符串的长度。所谓长度,就是包含多少个字符(不包括字符串结束标志 ‘\0’)。语法格式为:
strlen(arrayName);

3)、字符串连接函数 strcat
strcat 是 string catenate 的缩写,意思是把两个字符串拼接在一起,语法格式为:
strcat(arrayName1, arrayName2);
arrayName1、arrayName2 为需要拼接的字符串。

strcat 将把 arrayName2 连接到 arrayName1 后面,并删去 arrayName1 最后的结束标志 ‘\0’。这就意味着,arrayName1 的长度要足够,必须能够同时容纳 arrayName1 和 arrayName2,否则会越界。

strcat 返回值为 arrayName1 的首地址。

4)、字符串比较函数strcmp
strcmp 是 string compare 的缩写,意思是字符串比较,语法格式为:
strcmp(arrayName1, arrayName2);
arrayName1 和 arrayName2是需要比较的两个字符串。

字符本身没有大小之分,strcmp() 以各个字符对应的 ASCII 码值进行比较。strcmp() 从两个字符串的第 0 个字符开始比较,如果它们相等,就继续比较下一个字符,直到遇见不同的字符,或者到字符串的末尾。

返回值:若 arrayName1 和 arrayName2 相同,则返回0;若 arrayName1 大于 arrayName2,则返回大于 0 的值(1);若 arrayName1 小于 arrayName2,则返回小于0 的值(-1)

C语言还规定,数组名所代表的地址为第0个元素的地址,例如char c[10];,c就代表c[0]的地址。第0个元素的地址就是数组的起始地址,称为首地址。也就是说,数组名表示数组的首地址。

将数组所有元素初始化为0的方法是:int a[4] = {0};char b[4] = {0};不要以为不初始化就所有元素默认为0,是初始化的时候不给所有元素赋值,剩余元素才会默认为0.

位运算交换两数——利用异或^运算(所以程序中2^3不是2的3次方)

a=a^b;
b=a^b;
a=a^b;

位运算:就是直接对整数在内存中的二进制位进行操作
异或:位数对应的数相同取0,不同取1
也就是说a^b^b = a

下面的a和b都是整数类型,运算符号:

含义 C语言
按位与a & b
按位或a | b
按位异或 a ^ b
按位取反~a
左移 a

——————————————

重点说明:

左移
a<<b就表示把a转为二进制后左移b位(在后面添b个0)。例如100的二进制为1100100,而110010000转成十进制是400,那么100<<2 = 400。可以看出,a<<b的值实际上就是a乘以2的b次方,因为在二进制数后添一个0就相当于该数乘以2。
通常认为a<<1a * 2更快,因为前者是更底层一些的操作。因此程序中乘以2的操作请尽量用左移一位来代替。
定义一些常量可能会用到<<运算。你可以方便地用1 <<16 - 1来表示65535。很多算法和数据结构要求数据规模必须是2的幂,此时可以用<<来定义Max_N等常量。

右移
<<相似,a >> b表示二进制右移b位(去掉末b位),相当于a除以2的b次方(取整)。我们也经常用>>1来代替div 2,比如二分查找、堆的插入操作等等。想办法用>>代替除法运算可以使程序效率大大提高。最大公约数的二进制算法用除以2操作来代替慢得出奇的mod运算,效率可以提高60%。(div整除,mod取余)



交换不同的两数:a=a^b;b=a^b;a=a^b;
2的乘方:a*2 -> a<<
除以2:a/2 -> a>>2

需要注意的是,当a==b的时候,a=a^b会变成0


C语言非阻塞式键盘监听

监听键盘可以使用C语言的字符输入函数,例如 getchar、getch、getche 等。但阻塞式键盘监听非常不方便,尤其是在游戏中,往往意味着用户要不停按键游戏才能进行,所以一般采用非阻塞式键盘监听。

//使用getche函数监听键盘的例子:
#include <stdio.h>
#include <conio.h>
int main(){
    char ch;
    int i = 0;
    //循环监听,直到按Esc键退出
    while(ch = getch()){
        if(ch == 27){
            break;
        }else{
            printf("Number: %d\n", ++i);
        }
    }
    return 0;
}



//运行结果:
Number: 1  //按下任意键
Number: 2  //按下任意键
Number: 3
Number: 4
Number: 5  //按下Esc键

使用conio.h头文件中的kbhit函数可以实现非阻塞式键盘监听。

我们每按下一个键,都会将对应的字符放到键盘缓冲区,kbhit 函数会检测缓冲区中是否有字符,如果有字符返回非0值,没有返回0。但是kbhit不会读取字符,字符仍然留在缓冲区。请看下面的例子:

#include <stdio.h>
#include <windows.h>
#include <conio.h>
int main(){
    char ch;
    int i = 0;
    //循环监听,直到按Esc键退出
    while(1){
        if(kbhit()){
            ch = getch();
            if(ch == 27){
                break;
            }
        }
        printf("Number: %d\n", ++i);
        Sleep(1000);  //暂停1秒
        //Sleep 是“睡眠”的意思,用来让程序暂停执行一段时间,以毫秒记。
    }
    return 0;
}

//运行结果:
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5  //按下Esc键

每次循环,kbhit 会检测用户是否按下某个键(也就是检测缓冲区中是否有字符),没有的话继续执行后面的语句,有的话就通过 getch 读取,并判断是否是 Esc,是的话就退出循环,否则继续循环。

kbhit 之所以能够实现非阻塞式监听是因为它只检测字符,而不要求输入字符。

C语言获取随机数

可能大家在编程的时候需要电脑来获取一些随机的反应,这个时候我们可以使用随机数,比较常见的是 rand() 函数,它可以随机的产生 0 ~ rand_max 的随机数。rand_max 是一个很大的数字,具体关系到IDE和数据类型,我们一般的需要不可能超出它的范围。

#include <stdio.h>
#include <stdlib.h>
int main(){
    int a=rand();
    printf("%d\n",a);
    return 0;
}

编译后再运行几次,你会发现产生的随机数是相同的。实际上,rand() 函数产生的随机数是伪随机数,是根据一个数按照某个公式推算出来的,这个数我们称之为“种子”,但是这个种子在系统启动之后就是一个定值,我们需要用 srand() 来进行播种,即在int a前加一句:
srand((unsigned)time(NULL)); //这里利用时间进行播种,需要time.h

这样,我们就能得到不同的随机数,其实C语言中还有一个 random() 函数可以获取随机数,但是 random() 函数不是ANSI C标准,不能在VC等编译器通过,所以比较少用。

那如何产生一定范围的随机数呢?我们可以利用取模的方法:

int a=rand()%10;    //产生0~9的随机数,注意10会被整除
如果要规定上下限:
int a=rand()%51+13;    //产生13~63的随机数

分析:取模即取余,rand()%51+13我们可以看成2部分:rand()%51是产生0~50的随机数,后面+13保证a最小只能是13,最大就是50+13=63。

本节学习知识应用

排序算法——选择排序

#include <stdio.h> 
#include <stdlib.h>
#include <time.h>
const int MAXN = 10;

int main (void)
{
    int a[MAXN] = {0};
    int i = 0;
    int j = 0;

    srand( (unsigned)time(NULL) );//rand()函数产生的是伪随机数(产生原理是 F(种子值),也就是关于一个固定值的固定公式;
                                  //也就是程序运行第一次和第二次产生的随机数是一样的,所以我们要利用srand()进行播种;srand(unsigned seed)
    for (int k = 0; k < MAXN; k++) {
        a[k] = rand()%100 + 1;
    }

    printf("初始化数组:");
    for (i = 0; i < MAXN; i++) {
        printf("%-2d ", a[i]);
    }
    printf("\n");

    /*----------*/
    //选择排序原理:
    //1、从第一个数开始(a[0]),找到数组中最小的数的位置,然后交换第一个数和最小数
    //2、从第二个数开始(a[1]),找到剩余数中最小数的位置(对应整个数组第二小的数),然后交换第二个数和最小数
    //......
    //N-1、从第N-1个数开始(a[MAXN-1]))、最后一次,比较最后两数了。

    //外循环:对应找第一个位置上的最小数,第二个,...,当前N-1个都找好了,最后一个自然不用找了,
    //        故边界条件为 0 ~ MAXN-1 ,共 N-1次;
    //内循环:要完成的任务是:遍历数组元素找到最小值;交换该位置数和最小值所在位置数,如果是本身就不用交换;
    //        起点对应第一小的数放在第一个位置,第一个数也要遍历到;
    //        但是我是用第一个数最为参照进行比较的,所以起点i = j + 1,直接与下一个数进行比较即可,一直到最后一个数;
    //交换数:采用位运算中的异或运算交换两个不同的数;
    //        原理:a^b^b = a;

    /*-------  -*/
    for (j = 0; j < MAXN - 1; j++) {
        int min_index = j;
        for (i = j + 1; i < MAXN; i++) {
            if (a[min_index] > a[i]) {
                min_index = i;
            }
        }
        if (j != min_index) {
            a[j] = a[j] ^ a[min_index];
            a[min_index] = a[j] ^ a[min_index];
            a[j] = a[j] ^ a[min_index];
        }
    }

    printf("排序后数组:");
    for (i = 0; i < MAXN; i++) {
        printf("%-2d ", a[i]);
    }
    putchar('\n');

    return 0;
}
#include <cstdio> 
#include <cstdlib>
#include <ctime>
const int size = 10;

void select_sort(int a[], int MAXN) {

    printf("初始化数组:");
    for (int i = 0; i < size; i++) {
        printf("%-2d ", a[i]);
    }
    printf("\n");

    //核心部分 
    for (int j = 0; j < MAXN - 1; j++) {
        int min_index = j;
        for (int i = j + 1; i < MAXN; i++) {
            if (a[min_index] > a[i]) {
                min_index = i;
            }
        }
        if (j != min_index) {
            a[j] = a[j] ^ a[min_index];
            a[min_index] = a[j] ^ a[min_index];
            a[j] = a[j] ^ a[min_index];
        }
    }

    printf("排序后数组:");
    for (int i = 0; i < size; i++) {
        printf("%-2d ", a[i]);
    }
    putchar('\n');
}

int main (void)
{
    int a[size];
    //初始化数组
    srand( (unsigned)time(NULL) );
    for (int k = 0; k < size; k++) {
        a[k] = rand()%100 + 1;
    }
    select_sort(a, size);//排序 
    return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值