【C语言】程序设计入门——C语言

前言

本文是基于中国MOOC平台上的《程序设计入门——C语言(翁恺)》课程,所作的一篇课程笔记,便于后期进行系统性查阅和复习。


一、程序设计与C语言

本章的内容是提供一些背景知识,关于计算机、程序、编程语言,也关于C语言。是一篇导论,帮助学生建立关于计算机工作方式和编程语言的正确概念。

1.1 计算机与编程语言

1.1.1 计算机怎么做事情?编程语言是什么?

计算机如何解决问题?

需要一步步的告诉计算机如何解决问题。这里可以看到人与计算机的区别,对人(What to do),对计算机(How to do)。

计算机语言:

• 程序是用特殊的编程语言写出来表达如何解决问题的。

• 不是用编程语言来和计算机交谈,而是描述要求它如何做事情的过程或方法。

算法(计算机-程序-算法):

• 我们要让计算机做计算,就需要找出计算的步骤,然后用编程语言写出来。

• 计算机做的所有事情都叫做计算。

• 计算的步骤就是算法。

1.1.2 计算机的思维方式

重复是计算机最擅长的。

程序的执行有两种方案:

1.解释:借助一个程序,那个程序能试图理解你的程序,然后按照你的要求执行。

2.编译:借助一个程序,就像一个翻译,把你的程序翻译成计算机能懂的语言(即机器语言),然后这个机器语言写的程序就能直接执行。

解释语言vs编译语言

• 语言本无解释/编译之分(任何一种语言都可以解释执行,又可以编译执行)。

• 区分只在于传统和习惯(比如C语言常用编译执行,python常用解释执行)。

• 解释型语言有特殊的计算能力。

• 编译型语言有确定的运算性能。

1.2 C语言

C语言在工业界有重要的地位,在很多领域无可替代,几乎所有和硬件打交道的地方都要用C语言。本课程中按照C99来展开教学,但国内很多大学和计算机二级考试仍坚持更老的ANSL C,所以在课程中,凡C99和ANSL C不同之处,均会进行额外标注。

1.2.1 为什么是C?

• 现代的编程语言在语法上差异很小,其他语言几乎都是C-like语言。

• 语句的能力/适用领域主要是由库和传统所决定的。

1.2.2 简单历史

C的版本-标准

• 1989年ANSI发布了一个标准——ANSI C

• 1990年ISO接受了ANSI的标准——C89

• C的标准在1995和1999两次更新——C95和C99

1.3 第一个程序

大学中往往会让初学者使用Dev C++来学习C语言,而对于有一定基础的朋友大多会使用Visual Studio。

1.3.1 程序框架

#include<stdio.h>
int main{
    
    return 0;
}

•本课程中所有的程序都需要这一段

•直到学函数之前,我们的代码都只是在这个框架中间

•为了便于理解,目前不对程序框架进行解释,知道第一条即可


二、计算

2.1 变量

2.1.1 变量定义

int price=0;

•这一行,定义了一个变量。变量的名字是price,类型是int,初始值是0。

•变量是一个保存数据的地方,当我们需要在程序里保存数据时,就需要一个变量来保存它。用一个变量保存了数据,它才能参与到后面的计算中。

变量定义的一般形式:<数据类型名称><变量名称>;

•int a;

•double a,b,c;//定义了三个双精度浮点数

变量名称

•变量是一种“标识符”

标识符构造规则:只能由字母、数字和下划线组成,数字不能出现在首位,C语言的关键字/保留字不可以用作标识符。

在变量定义上,ANSI C与C99有一点不同:

ANSI C只能在代码开头的地方定义变量

int price;
int change= 0;
pritf("请输入金额(元):");
scanf("%d",&price);
change=100-price;

 而C99可以在代码中间定义变量

int price;
pritf("请输入金额(元):");
scanf("%d",&price);
int change=100-price;

2.1.2 变量的赋值和初始化

C语言中赋值与初始化的差异并不大,但在其他语言,如C++这两种之间差异会比较大。

变量赋值

•price=0;

•这是个赋值语句,“=”是赋值运算符,表示将“=”右边的值赋给左边的变量。

变量初始化

•<数据类型名称><变量名称>=<初始值>;

•int price=0;

•int amount=100;

•组合变量定义的时候,也可以在这个定义中单独给单个变量赋初值,如:

•int price=0,amount=100;

变量类型

C语言是一种有类型的语言。所有变量在使用之前必须定义或声明,所有变量必须有具体的数据类型。数据类型表示在变量中可以存放什么样的数据,变量中只能存放指定类型的数据,程序运行过程中也不能改变变量的类型。

2.1.3 常量vs变量:不变的量是常量

int change=100-price;

•100直接写在程序里,我们称作直接量。

•更好的方式,是定义一个常量:const int AMOUNT=100;(C99才有的用法)//便于理解、修改

const

•是一个修饰符,加在int的前面,用来给这个变量加一个const(不变的)的属性。这个const的属性表示这个变量的值一旦初始化,就不能再修改了。

2.2 数据类型

10和10.0在C中是两个完全不同的数,10.0是浮点数。

浮点数

•C语言中,人们借助浮点数来表达所有的带小数点的数。

•当浮点数和证书放在一起运算时,C语言会将整数转换成浮点数,然后进行浮点数的运行。进而得到更精准的结果——浮点数。

2.3 表达式

2.3.1 表达式

表达式

•一个表达式是一系列运算符和算子的组合,用来计算一个值。

•运算符是指进行运算的动作,比如加法运算符“+”运算符、减法“-”运算符。

•算子是指参与运算的值,这个值可能是常熟,也可能是变量,还可能是一个方法的返回值。

2.3.2 运算符优先级

运算符优先级

初等运算符>单目运算符>算术运算符(先乘除、后加减)>关系运算符>逻辑运算符(不含!)>条件运算符>赋值运算符>逗号运算符

以上的优先级,从左往右递减。

赋值运算符

•赋值也是运算,也有结果

•a=6的结果是a被赋予的值,也就是6

•a=b=6——>a=(b=6)

2.3.3 交换变量

如何交换两个变量的值?

int a=6;
int b=5;
int c;
c=a;
a=b;
b=c;

2.3.4 复合赋值和递增递减

复合赋值

•5个算术运算符(+-*/%),可以和赋值运算符“=”结合起来,形成复合赋值运算符:“+=”、  “-=”、“*=”、“/=”、“%=”。

•total+=5;

•total=total+5;//这两个表达式的含义是一样的

•注意两个运算符之间不要有空格

递增递减运算符

•“++”和“--”是两个单目运算符,叫做递增和递减运算符。

•与之搭配使用的算子必须是变量,它们的作用是给这个变量+1或-1。

•i++;

•i+=1;

•i=i+1;//这三个表达式的含义是一样的

 前缀后缀

•++和--可以放在变量前面,叫做前缀形式,也可以放在变量后面,叫做后缀形式

•a++的值是a+1之前的值,而++a的值是a+1以后的值。

•无论是a++还是++a,a的值都是a+1以后的值。

int i=0;
printf("i++=%d\n", i++);     //i++=0
printf("i=%d\n", i);         //i=1
printf("++i=%d\n", ++i);     //++i=2
printf("i=%d\n", i);         //i=2
表达式运算表达式的值a的值
a++给a加1a原来的值a+1以后的值
++a给a加1a+1以后的值a+1以后的值
a--给a减1a原来的值a-1以后的值
--a给a减1a-1以后的值a-1以后的值

 三、判断与循环

本课程在该章仅对判断与循环语句进行了基础讲解,若想了解更高级的语句,请见第四章。

3.1 判断

3.1.1 if语句

if(条件成立){
   //语句1
}

 3.1.2 判断的条件

条件

•计算两个值之间的关系,叫做关系运算。

•关系运算符的结果:当两个值的关系符合关系运算符的预期时,关系运算的结果为整数1,否则为整数0。 

关系运算符

意义

==相等
!=不相等
>大于
>=大于等于
<小于
<=小于等于

3.1.3 否则:如果条件不成立?

if(条件不成立){
   //语句1
}else{
   //语句2
}

3.1.4 if和else后面也可以没有{}而是一条语句

if(条件不成立)
   //语句1
else
   //语句2

提示:这样进行编程是不好的习惯!if和else后面一定要用{}! 

如果不使用{},if和else只能执行if和else后的单个语句,不便于执行多条语句的情况。使用{},能够明确标识出哪些语句属于if和else语句的范围,避免了可能的歧义,增加了代码的可读性和可维护性。

3.2 循环

循环语句选择小tips:如果有固定次数,用for;

                                  如果必须执行一次,用do-while;

                                  其他情况,用while。

3.2.1 while循环

while循环基本语句:

while(循环条件){
      //循环体
}

•while循环的意思:当条件满足时,不断重复循环体内的语句,直至条件不满足。

•循环执行之前判断是否继续循环,所以有可能循环一次也没有被执行

3.2.2 do-while循环

do-while循环基本语句:

do{
    //循环体
}while(循环条件)

•先执行循环体,然后再检查循环条件是否成立,若成立再执行循环体。

3.2.3 for循环

for循环基本语句:

for(循环变量赋初值;循环条件;循环变量增值){
            //循环语句
}

for语句能用于两种情况:1.循环次数已经确定。2.循环次数不确定而只给出循环结束条件。


四、进一步的判断与循环

4.1 逻辑类型和运算

4.1.1 bool类型

使用方式:

•头文件#include<stdbool.h>

•之后就可以使用bool和true、false

#include<stdio.h>
#include<stdbool.h>
int main(){
    bool b=6>5;   //b为true
    bool t=false; //t为false
    printf("%d %d",b,t);//输出1,0
    return 0;
}

4.1.2 逻辑运算

•逻辑运算是对逻辑量进行运算,结果只有0或1

•逻辑量是关系运算或逻辑运算的结果

运算符描述示例结果
逻辑非!a

如果a是true,结果就是false;

如果a是false,结果就是true。

&&

逻辑与a&&b如果a和b都是true,结果就是true,否则结果是false
||逻辑或a||b如果a和b都是false,结果就是false,否则结果是true

4.1.3 条件运算和逗号运算

条件运算符(表达式1?表达式2:表达式3)

•条件?条件满足时的值:条件不满足时的值

•条件运算符的优先级高于赋值运算符,但低于其他所有运算符

嵌套条件运算符

•条件运算符是自右向左结合的

•w<x?x+w:x<y?x:y

•嵌套条件运算符不利于理解、调试,不推荐使用!

逗号运算符

•逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。

•i=3+4,5+6;与i=(3+4,5+6);//i=7与i=11

•该运算符常用于for语句

4.2 级联和嵌套的判断

4.2.1 嵌套的if-else

找三个数的最大?

    int a, b, c;
	int max = 0;
	scanf_s("%d%d%d", &a, &b, &c);
	if (a > b) {
		if (a > c) {
			max = a;
		}
		else {
			max = c;
		}
	}
	else {
		if (b > c) {
			max = b;
		}
		else {
			max = c;
		}
	}
	printf("三位数中最大为%d", max);

4.2.2 级联的if-else

分段函数?(单一出口比多出口的代码更优秀)

	int x, f;
	scanf("%d", &x);
	if (x > 0) {
		f = x + 1;
	}
	else if (x == 0) {
		f = 0;
	}
	else {
		f = 2*x - 1;
	}
	printf("%d", f);//单一出口
	return 0;0;
	int x, f;
	scanf_s("%d", &x);
	if (x > 0) {
		printf("%d",f = x + 1);//多出口
	}
	else if (x == 0) {
		printf("%d",f = 0);
	}
	else {
		printf("%d", f = 2*x - 1);
	}
	return 0;

4.3 多路分支

switch-case基本语句:

switch(控制表达式)
{
  case 常量: 
      语句;
      break;//注意break不要忘掉!
  default:
      语句;
      break;
}

4.4 循环的例子

编程的难点:把问题转换为程序。(变量,算法,流程图,程序)

算平均数

算法:1.初始化变量sum和count为0;

           2.读入number;//输入的数个数是不明确的,如何判断输入结束了?

           3.如果number不是-1,则将number加入sum,并将count+1,回到2;

           4.如果number是-1,则计算和输出sum/count(注意换成浮点来计算)。

	int sum = 0, count = 0;
	int number;
	
	scanf_s("%d", &number);
	while(number != -1) {//使用while循环
		sum += number;
		count++;
		scanf_s("%d", &number);
	}
		printf("%.2f", sum*1.0 / count);
	int sum = 0, count = 0;
	int number;
	
	do {//使用do-while循环
		scanf_s("%d", &number);
		if (number != -1) {
			sum += number;
			count++;
		}
	} while (number != -1);
		printf("%.2f", sum*1.0 / count);

 猜数

算法:1.定义变量number和x=rand();

           2.读入number;

           3.如果number不等于x,进入循环判断猜大猜小,返回2;

           4.如果number等于x,则直接跳过循环,输出恭喜猜对了。

#include<stdio.h>
#include<stdlib.h>
int main() {
	int number;
	int x = rand();
	scanf_s("%d", &number);
	while (number != x) {
		if (number > x) {
			printf("不好意思,猜大了。\n");
		}
		else {
			printf("不好意思,猜小了。\n");
		}
		scanf_s("%d", &number);
	}
		printf("恭喜你!猜对了!\n");
	return 0;
}

整数求逆

#include<stdio.h>
int main() {
	int number,x=8;
	scanf_s("%d", &number);

	while (number != 0){
		x = number % 10;
		number /= 10;
		printf("%d",x);
	} 

	return 0;
}

该代码无法在number为“007”时,得到“700”?

4.5 判断和循环常见错误

if语句常见错误

•忘了大括号,

•if后面错误加分号

•错误使用==和=

•令人迷惑的else


五、循环控制

5.1 循环控制

判断数是否是素数?

难点:1.判断整除——取余%为0,说明是整除。

           2.仅输出一句话——加入一个新的变量来判断输出。

           3.循环控制,在第一次整除就可以判断数不是素数了——break跳出循环体。

#include <stdio.h>
int main()
{
	int x,i;
	scanf_s("%d", &x);
	int isPrime = 1;
	for (i = 2; i < x; i++) {
		if (x % i == 0) {
			isPrime = 0;
			break;
		}
	}
	if (isPrime == 0) {
		printf("不是素数");
	}
	else {
		printf("是素数");
	}
	return 0;
} 

5.2 多重循环

5.2.1 嵌套循环

写程序输出100以内的素数

#include <stdio.h>
int main()
{
	int x, i;
	for (x = 2; x < 100; x++) {
		int isPrime = 1;
		for (i = 2; i < x; i++) {
			if (x % i == 0) {
				isPrime = 0;
				break;
			}
		}
		if (isPrime != 0) {
			printf("%d ", x);
		}
	}
	printf("\n");
	return 0;
}

上述正是嵌套循环,需要注意在嵌套循环中内部和外部的循环变量应该不同,避免出现混淆。

5.2.2 跳出循环

如何从循环中跳出(break、continue、goto)

break:只能跳出一层循环体,如需跳出嵌套循环,可使用接力break。

continue:跳过循环体中剩余的语句而强制进入下一次循环,并没有跳出循环体。

goto:goto XX;XX:位于循环体外。//goto最好只用于跳出循环,不要用于其他情况

5.3 循环应用

5.3.1 前n项求和

	int n, i;
	float f=0.0;
	scanf_s("%d", &n);
	for (i = 1; i <= n; i++) {
		f += 1.0 / i;
	}
	printf("f(%d)=%f",n, f);

	int n, i;
	float f=0,sign=1.0;
	scanf_s("%d", &n);
	for (i = 1; i <= n; i++) {
		f += sign / i;
		sign=-sign;
	}
	printf("f(%d)=%f",n, f);

5.3.2 求最大公约数

输入两个数a和b,输出它的最大公约数

有两种方法:枚举法,辗转相除法

枚举法:1.设t=1;

              2.如果a和b都能被t整除,则记下这个t;

              3.t+1重复第二步,知道t等于a或者b;

              4.那么,记下的最大的可以同时整除a和b的t就是最大公约数。

	int a, b;//枚举法
	scanf_s("%d%d", &a, &b);
	int min;
	if(a<b) {
		min = a;
	}else{
		min = b;
	}
	int ret = 0;
	int i;
	for (i = 1; i < min; i++) {
		if (a % i == 0) {
			if (b % i == 0) {
				ret = i;
				//求最小公约数时可以在检测到一个公约数时跳出循环goto out;
			}
		}
	}
	//out:
	printf("%d和%d的最大公约数是%d\n",a,b,ret);

辗转相除法:1.如果b等于0,计算结束,a就是最大公约数;

                      2.否则,计算a除以b的余数,让a=b,而b等于那个余数;

                      3.回到第一步。

	int a, b;
	scanf_s("%d%d", &a, &b);
	int t=0;
	while(b!=0){
		t = a % b;
		a = b;
		b = t;
	}
	printf("a和b的最大公约数是%d\n", a );

5.3.3 整数分解

输入一个非负整数,正序输出它的每一位数字

我的解答:

	int x,t=1;
	int i = 0;
	scanf_s("%d", &x);
	int number = x;
	while (number != 0) {
		t = number % 10;
		number /= 10;
		i++;
	}//求出数的位数
	i = i - 1;
	for (; i != -1; i--) {
		t = x / pow(10, i);
		x -= t * pow(10, i);
		printf("%d ", t);
	}

翁恺老师的解答:个人感觉有点复杂,这里就不列出来了。

思路是先得到逆序的数字列,再输出正序的数字列。


六、数组与函数

数组是长度固定的数据结构,用来存放指定类型的数据。一个数组里可以有很多个数据,所有数据的类型都是相同的。

如果我们对输入数组的数据个数是未知的,如何安全的把数据存放进数组?

【C语言】如果我们对输入数组的数据个数是不知道的,如何安全的把数据存放进数组?-CSDN博客

6.1 数组

6.1.1 初试数组

写程序计算用户输入数字的平均数,并输出所有大于平均数的数。

	int x = 0;
	double sum = 0;
	int cnt = 0;
	int number[100];//定义数组
	scanf_s("%d", &x);
	while (x != -1) {
		number[cnt] = x;//对数组中的元素赋值
		sum += x;
		cnt++;
		scanf_s("%d", &x);
	}
	int average,i;
	average = sum / cnt;
	for (i = 0; i != cnt; i++) {
		if (number[i] > average) {//使用数组中的元素
			printf("%d ", number[i]);
		}
	}

6.1.2 定义数组

 •<类型>变量名称[元素数量]

   int grades[100];

   double weigh[20];

•元素数量必须是整数

•C99之前:元素数量必须是编译时刻确定的字面量

 数组是一种容器(存储东西的东西),特点是:

•其中所有的元素具有相同的数据类型

•一旦创建,不能改变大小

•数组中的元素在内存中的连续依次排列的

int a[10]

•一个int的数组

•10个单位:a[0], a[1],a[2],...,a[9]

•每个单位就是一个int类型的变量

•可以出现在赋值的左边或者右边:a[2]=a[1]+6;

数组的单位

•数组的每个单元就是数组类型的一个变量

•使用数组时放在[]中的数字叫做下标或索引,下标从零开始计数

•程序员要保证程序只使用有效的下标值:[0,数组的大小-1]

长度为0的数组?

•Int a[0];

•可以存在,但是无用 

6.1.3 用数组做散列计算

 写程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-1表示结束。

const int number=10;//数组的大小
int x;
int count[number];//定义数组
int i;
for (i = 0; i < number; i++) {//用for循环初始化数组
	count[i] = 0;
}
scanf("%d", &x);
while (x != -1) {
	if (x >= 0 && x <= 9) {
		count[x]++;//数组参与运算
	}
	scanf("%d", &x);
}
for (i = 0; i < number; i++) {//遍历数组做输出
	printf("%d:%d\n", i, count[i]);
}

6.2 函数的定义与使用

6.2.1 初见函数

求和函数

#include<stdio.h>

	void sum(int begin, int end) {
		int i;
		int sum = 0;
		for (i = begin; i <= end; i++) {
			sum += i;
		}
		printf("%d到%d的和是%d\n", begin, end, sum);
	}

int main(){
	sum(1, 10);
	sum(23, 42);

	return 0;
}

6.2.2 函数的定义与使用

 什么是函数?

•函数是一块代码,接受零个或多个参数,做一件事情,并返回零个或一个值

•可以想象成数学中的函数:y=f(x)

函数定义

void sum(int begin, int end) //函数头
{
		int i;
		int sum = 0;
		for (i = begin; i <= end; i++) {
			sum += i;
		}
		printf("%d到%d的和是%d\n", begin, end, sum);
}//函数体

函数头:void返回类型,sum函数名,int begin,int end参数表

调用函数

 •函数名(参数值)

•()起到了表示函数调用的重要作用

•即使没有参数也要()

6.2.3 从函数中返回

 从函数中返回值

•return停止函数的执行,并送回一个值

•return;

•return 表达式;//return会把表达式送回调用函数的位置

 从函数中返回值

•该值可以赋值给变量

•该值可以再传递给函数

•该值也可以丢弃

没有返回值的函数

•void 函数名(参数表)

•不能使用带值的return

•可以没有return

调用时不能做返回值的赋值 

6.3 函数的参数和变量

6.3.1 函数原型

函数先后关系

前文把函数写在调用函数上面,是因为C的编译器自上而下顺序分析代码。

如果不将函数整体放在调用函数之前?

#include<stdio.h>

void sum(int begin, int end);//函数的原型声明
	
int main(){
	int x = 9;
	sum(x, 10);
	sum(23, 42);

	return 0;
}
void sum(int begin, int end) //函数的定义
{
		int i;
		int sum = 0;
		for (i = begin; i <= end; i++) {
			sum += i;
		}
		printf("%d到%d的和是%d\n", begin, end, sum);
	}

函数原型

 •函数头,以分号;结尾,就构成了函数的原型

void sum(int begin, int end);

 •函数原型的目的:告诉编译器这个函数长什么样(名称、参数数量及类型、返回类型)

 •旧标准习惯把函数原型写在调用它的函数里面

 •现在一般写在调用它的函数前面

6.3.2 参数传递

 调用函数

 •如果函数有参数,调用函数必须传递给它的数量、类型正确的值

 •可以传递给函数的值是表达式的结果,这包括字面量、变量、函数的返回值、计算的结果

int a,b,c;
a=5;
b=6;
c=max(10,12);
c=max(a,b);
c=max(c,23);
c=max(max(12,34),34);
c=max(23+98,b);

如果调用函数所给值与参数类型不匹配,会如何?

 •编译器会悄悄把类型转换好,但这很可能不是编程者所期望的。

 •后续语言,C++/Java在这方面会很严格。

传值

 •C语言在调用函数时,永远只能传值给函数。

 •每个变量有自己的变量空间,参数也位于这个独立的空间中,和其它函数没有关系。

 •过去,对于函数参数表中的参数,叫做“形式参数”,调用函数给的值,叫做“实际参数”

(但这种说法容易对初学者造成误导,故不建议继续使用这种说法)

 •我们认为,调用函数给的就是值,函数参数表中的参数就是参数,两者是值与参数的关系 

6.3.3 本地变量

本地变量

 •函数的每次运行,就产生一个独立的变量空间,在这个空间中的变量,是函数这次运行所独有的,称作本地变量。

 •定义在函数内部的变量就是本地变量。

 •参数也是本地变量。

变量的生存期和作用域

 •生存期:什么时候这个变量开始出现,到什么时候它消亡了。

 •作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)。

 •对于本地变量而言,这两个量都是:大括号内——块。 

本地变量的规则

 • 本地变量是定义在块内的。

 •程序运行进入这个块之前,其中的变量不存在;离开这个块,其中的变量就消失了。

 •本地变量不会被默认初始化。

6.3.4 杂事 

没有参数时,如何写参数?

 •void f(void)

 •void f()//编译器会默认参数未知,而非没有参数

 •建议使用void f(void);

调用函数中的逗号和逗号运算符怎么区分?

 •f(a,b);//这里的逗号就是单纯的标点符号

 •f(a,b);//这里的逗号就是逗号运算符

可以在函数里面定义另一个函数吗?

  •不可以,C语言不允许嵌套定义 

6.4 二维数组

int a[3][5];

•通常理解为a是一个3行5列的矩阵

a[1][1]a[1][2]a[1][3]a[1][4]a[1][5]
a[2][1]a[2][2]a[2][3]a[2][4]a[2][5]
a[3][1]a[3][2]a[3][3]a[3][4]a[3][5]

二维数组的遍历

•a[i][j]是一个Int,表示第i行第j列的单元

for(i=0;i<3;i++){
    for(j=0;j<5;j++){
        a[i][j]=i*j;
    }
}

二维数组的初始化

int a[][5]={
    {0,1,2,3,4},
    {2,3,4,5,6},
};

•列数是必须给出的,行数可以由编译器来数

•每行一个{},逗号分隔

•最后的逗号可以存在,有古老的传统

•如果省略,表示补零


七、数组运算

7.1 数组运算

7.1.1 数组运算

搜索:在一组给定的数据中,如何找出某个数据是否存在?

#include<stdio.h>

int search(int x, int a[], int length);

int main() {
	int a[] = { 2.4,6,8,9,4,5 };//数组的集成初始化
	int x;
	int loc;
	printf("请输入一个数字:");
	scanf_s("%d", &x);
	loc = search(x, a, sizeof(a) / sizeof(int));
	if (loc != -1) {
		printf("%d在%d个位置上\n", x, loc);
	}
	else {
		printf("%d不存在\n", x);
	}
	return 0;
}
int search(int key, int a[], int length) {
	int ret = -1;
	for (int i = 0; i < length; i++) {
		if (a[i] == key) {
			ret = i;
			break;
		}
	}
	return ret;
}

数组的集成初始化

	int a[] = { 2.4,6,8,9,4,5 };

•仅给数组中的几个值赋值,其他初始化为0

int a[13]={[5]=6,7,[10]=23};//C99才能这样
                            //a[13]={0,0,0,0,6,7,0,0,0,23,0,0,0}
int a[]={[5]=6,7,[10]=23};//a[13]={0,0,0,0,6,7,0,0,0,23}

数组的大小

•sizeof(a)/sizeof(a[0])//sizeof()所得是数组的字节数

•sizeof给出整个数组所占据的内容大小,单位是字节

数组的赋值

 •把一个数组赋值给另一个数组(不可行)

int a[]={2,23,5,3,3,4,5,43,24,4};
int b[]=a;

 •数组本身不能赋值

 •要把一个数组的所有元素交给另一个数组,必须采用遍历

int a[]={2,23,5,3,3,4,5,43,24,4};
const int number=sizeof(a)/sizeof(a[0]);
int b[number]={0};
for(int i=0;i<number;i++){
    b[i]=a[i];
}

7.1.2 数组例子:素数

方法一:从2到x-1测试是否可以整除。

#include<stdio.h>

int search(int x);

int main() {
	int x;
	scanf_s("%d", &x);
	if (search(x)) {
		printf("%d是素数", x);
	}
	else {
		printf("%d不是素数", x);
	}
	return 0;
}

int search(int x) {
	int ret = 1;
	if (x == 1) ret = 0;
	for (int i = 2; i < x; i++) {
		if (x % i == 0) {
			ret = 0;
            break;
		}
	}
	return ret;
}

方法二:偶数必然不是素数,去掉偶数后从3到x-1,每次加2测试是否整除。

且循环无需到达x-1,到达\sqrt{x}就够了。(数学知识)

int search(int x) {
	int ret = 1;
	int i;
	if (x == 1 || (x % 2 == 0 && x != 2)) ret = 0;
	for (i = 3; i < sqrt(x); i+=2) {
		if (x % i == 0) {
			ret = 0;
			break;
		}
	}
	return ret;
}

方法三:判断是否能被已知的且<x的素数整除(构造素数表)。

#include<stdio.h>

int isPrime(int x, int knownPrimes[], int number0fKnownPrimes) ;

int main() {
	const int maxNumber = 25;
	int isPrime[maxNumber];
	int i;
	int x;
	for (i = 0; i < maxNumber; i++) {
		isPrime[i] = 1;
	}
	for (x = 2; x < maxNumber; x++) {
		if (isPrime[x]) {
			for (i = 2; i * x < maxNumber; i++) {
				isPrime[i * x] = 0;
			}
		}
	}
	for (i = 2; i < maxNumber; i++) {
		if (isPrime[i]) {
			printf("%d\t", i);
		}
	}
	return 0;
}

int isPrime(int x, int knownPrimes[], int number0fKnownPrimes) {
	int ret = 1;
	int i;
	for (i = 0; i < number0fKnownPrimes; i++) {
		if (x % knownPrimes[i] == 0) {
			ret = 0;
			break;
		}
	}
	return ret;
}

7.2 线性搜索

7.2.1 线性搜索

搜索

•在一个数组中找到某个数的位置(或确认是否存在)

•基本方法:遍历

#include<stdio.h>
int search(int key,int a[],int len) 
{
	int ret = -1;
	for (int i = 0; i < len; i++) {
		if (key == a[i]) {
			ret = 0;
			break;
		}
	}
	return ret;
}

int main(void){
	int ret=6;
	int a[] = { 2,5,3,6,2,654,24,23,45,24,13,1435,334,13,43,42425,25,3,635,3,2 };
	int r=search(ret, a, sizeof(a) / sizeof(a[0]));
	printf("%d\n", r);

	return 0;
}

7.2.2 搜索的例子

例子:查找给定金额对应的硬币名称。

1penny
5

nickel

10dime
25quarter
50half-dollar
#include<stdio.h>

int amount[] = { 1,5,10,25,50 };
char* name[] = { "penny","nickel","dime","quarter","half - dollar"};

int search(int key, int a[], int len)
{
	int ret = -1;
	for (int i = 0; i < len; i++) {
		if (key == a[i]) {
			ret = i;
			break;
		}
	}
	return ret;
}

int main() {
	int k = 50;
	int r = search(k, amount, sizeof(amount) / sizeof(amount[0]));
	if (r > -1) {
		printf("%s\n", name[r]);
	}
	return 0;
}

这个代码中存在两个割裂的数组,这样的方式对cache很不友好。

最好是两个数组能放在一起,不要割裂开来,如何改进?

 改进方法:用一个结构把两个数组联合起来

#include<stdio.h>

int amount[] = { 1,5,10,25,50 };
char* name[] = { "penny","nickel","dime","quarter","half - dollar"};
struct {
	int amount;
	char* name;
}coins[] = {
	{1,"penny"},
	{5,"nickel"},
	{10,"dime"},
	{25,"quartes"},
	{50,"half-dollar"},
};

int search(int key, int a[], int len)
{
	int ret = -1;
	for (int i = 0; i < len; i++) {
		if (key == a[i]) {
			ret = i;
			break;
		}
	}
	return ret;
}

int main() {
	int k = 50;
	//int r = search(k, amount, sizeof(amount) / sizeof(amount[0]));
	for (int i = 0; i < sizeof(coins) / sizeof(coins[0]); i++) {
		if (k == coins[i].amount) {
			printf("%s\n", coins[i].name);
			break;
		}
	}
		return 0;
}

7.2.3 二分搜索

引入:线性搜索最大的问题就是没有效率,遇到数据较大的情况就很慢。

解决方案:在数组排好序的情况下,通过比较中间元素与目标元素的大小关系,缩小搜索范围。

#include<stdio.h>

int search(int x, int a[], int len) {
	int ret=-1;//表达结果的参数
	int left = 0; 
	int right = len - 1;
	while (right>=left) {
		int mid = (left+right) / 2;
		if (a[mid] == x) {
			ret = mid;
			break;
		}
		else if (a[mid] > x) {
			right = mid-1;
		}
		else {
			left = mid+1;
		}
	}
	return ret;
}

int main() {
	int x ;
	scanf_s("%d", &x);
	int a[] = {1,2,3,4,5,6,7,8,9};
	int len = sizeof(a) / sizeof(a[0]);
	if (search(x, a, len) == -1) {
		printf("未找到");
	}
	else {
		printf("在a[%d]找到%d\n", search(x, a, len-1), x);
	}
	return 0;
}

7.3 排序初步

二分法确实很不错,但给定数组是无序的怎么办?

解决方案:先把无序的数组,排列成有序的。

#include<stdio.h>
//求数组最大值
int max(int *a,int len) {
	int max = 0;
	for (int i = 1; i < len; i++) {
		if (a[i] > a[max]) {
			max = i;
		}
	}
	return max;
}

int main() {
	int a[] = {4,2,9,3,1,4,64,2};
	int len = sizeof(a) / sizeof(a[0]);
//将最大值与a[len-1]交换位置,重复该过程
	for (int i = len - 1;i>0; i--) {
		int maxid = max(a, i+1);
	    int t = a[maxid];
		a[maxid] = a[i];
		a[i] = t;
	}
//遍历数组,输出数组
	for (int j = 0; j < len; j++) {
		printf("%d\n", a[j]);
	}
	return 0;
}


八、指针与字符串 

8.1 指针

8.1.1 取地址运算:&运算符取得变量的地址

sizeof

•是一个运算符,给出某个类型或变量在内存中所占据的字节数

•sizeof(int)

•sizeof(i)

运算符&

•scanf("%d",&i);里的&

•获取变量的地址,它的操作数必须是变量

•地址大小是否与int相同取决于编译器

•int i;printf("%p",&i);//输出i的地址,记得要使用%p

&不能取的地址

•&不能对没有地址的东西取地址,必须取明确变量的地址

•&(a+b)

•&(a++)

•&(++a)

对数组进行取地址

	int a[] = { 1,2,3,3,4 };

	printf("%p\n",&a);     //显示&a和a和a[0]的地址一致,a[0],a[1],a[2]之间相差四个字节
	printf("%p\n", a);
	printf("%p\n", &a[0]);
	printf("%p\n", &a[1]);
	printf("%p\n", &a[2]);

8.1.2 指针:指针变量就是记录地址的变量

 指针

•就是保存地址的变量

  int i;

  int* p=&i;

  int* p,q;//p是一个指针,指向一个int;q一个普通的整型变量

  int *p,q;//p是一个指针,指向一个int;q一个普通的整型变量

指针变量

 •变量的值是内存的地址

•普通变量的值是实际的值

•指针变量的值是具有实际值的变量的地址

作为参数的指针

•void f(int *p);

•在被调用的时候得到了某个变量的地址

•int i=0;f(&i);

•在函数里面可以通过这个指针访问外面的这个i 

已知一个地址,想要访问那个地址上的变量

•使用运算符*,*是一个单目运算符,用来访问指针的值所表示的地址上的变量。

•可以做右值,也可以做左值

int k=*p;

*p=k+1; 

8.1.3 指针与数组:为什么数组传进函数后的sizeof不对了

 传入函数的数组成了什么?

•函数参数表的数组实际上是指针

•sizeof(a)==sizeof(int*);

•但是可以用数组的运算符[]进行运算

以下四种函数原型是等价的

•int sum(int *ar,int n);

•int sum(int *,int);

•int sum(int ar[],int n);

•int sum(int [],int); 

数组变量是特殊的指针

•数组变量本身表达地址,所以

  int a[10];int *p=a;//无需用&取地址 

•但是数组的单元表示的是变量,需要用&取地址

  int a[10];int *p=&a[1];

•a==&a[0]

•[]运算符可以对数组做,也可以对指针做:*p==p[0]==a[0]//p[0]意为如果我以为p所指的地方是个数组,那么它就是p所指位置上的第一个整数取出来作为p[0]。

•数组变量是const的指针,所以不能被赋值。

8.2 字符类型

8.2.1 字符类型

•char是一种整数,也是一种特殊的类型:字符。这是因为:

•用单引号表示的字符字面量:'a'、'1'。

•''也是一个字符

•printf和scanf中用%c来输入输出字符

ASCII表

•字母在其中顺序排列

•大写字母和小写字母分开排列,并不在一起

char c='A';
c++;//ASCII码加一,c='B'
printf("%c\n",c);//输出B

8.2.2 逃逸字符

字符意义字符意义
\b回退一格\"双引号
\t到下一个表格位\'单引号

\n

换行\\反斜杠本身
\r回车

8.3 字符串

8.3.1 字符串

字符数组

•char world[]={'H','e','l','l','o','!'};

word[0]word[1]word[2]word[3]word[4]word[5]
Hello!

•这不是C语言的字符串,因为不能用字符串的方式做计算

字符串

•char world[]={'H','e','l','l','o','!','\0'};//多一个字符'\0'

•以0(整数0)结尾的一串字符(0和'\0'是一样的,但是和'0'不同)

•0标志字符串的结束,但它不是字符串的一部分(计算字符串长度是不包含这个0)

•字符串以数组的方式存在,以数组或指针的方式访问(更多是以指针的方式)

•string.h里有很多处理字符串的函数

word[0]word[1]word[2]word[3]word[4]word[5]word[6]
Hello!\0

字符串变量

•char *str="Hello";//一个叫str的指针,它指向了一个字符数组,这里面放的内容是Hello

 char word[]= "Hello";//这有个叫word的字符数组,它里面的内容是Hello

 char line[10]="Hello";//这有个叫line的字符数组,大小有十个字节那么大,向里面放入Hello

"Hello"

 •"Hello"会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表结束的0

8.3.2 字符串变量

char *s="Hello World!";

 •s是一个指针,初始化指向一个字符串常量

 •由于这个常量所在的地方(此处只读不可写),所以实际上s是const char* s

 •对s所指的字符串做写入会导致严重的后果

 •如果需要修改字符串,应该用数组

   char s[]="Hello World!";

字符串的两种写法:指针or数组

  •指针:char *s="Hello World!";这个字符串不知道在哪(用于处理参数、动态分配空间)

  •数组:char s[]="Hello World!";这个字符穿在这里,作为本地变量空间自动被回收。

如果要构造一个字符串——>数组

如果要处理一个字符串——>指针

char*是字符串?

   •字符串可以表达为char*的形式

   •char*不一定是字符串

   •本意是指向字符的指针,可能指向的是字符的数组(就像int*)

   •只有它所指的字符数组有结尾的0,才能说它所指的是字符串

8.4 字符串计算

8.4.1 字符串输入输出

字符串赋值

   char *t="title";

   char *s;

   s=t;

   •并没有产生新的字符串,只是让指针s指向t所指的字符串,对s的任何操作就是对t做的

字符串的输入输出

char string[8];

scanf("%s",string);

printf("%s",string); 

   •scanf读入一个单词(到空格、Tab或回车为止)

   •这里scanf是不安全的,因为不知道读入内容长度,可能会超过string[8]

如何安全的输入字符串?

char string[8];

scanf("%7s",string);

   •在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一

   •下一个scanf会在上一个scanf结束之后再接着读入 

常见错误

char *string;//这里只是定义了一个指针变量

scanf("%s,string); 

   •误以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用

   •由于没有对string初始化为0,所以不一定每次运行都出错

空字符串

char buffer[100]="";

   •这是一个空字符串,buffer[0]=='\0 '

char buffer[]="";

   •这个数组的长度只有1

 8.4.2 常见的字符串函数

头文件#include<string.h>

 strlen

   •size_t strlen(const char*s);

   •返回s的字符串长度(不包括结尾的0)

strcmp

   •int strcmp(const *s1,const char *s2);

   •比较两个字符串,返回:

                                         0:s1==s2

                                    正数:s1 > s2

                                    负数:s1 < s2

strcpy

   •char *strcpy(char *restrict dst,const char *restrist src);

   •把src的字符串拷贝到dst

   •restrict表明src和dst不重叠

   •返回dst

strcut

   •char *strcat(char *restrict s1, char *restrict s2);

   •把s2拷贝到s1的后面,接成一个长的字符串

   •返回s1

   •s1必须具有足够的空间

strcpy和strcat都可能出现安全问题

   •如果目的地没有足够的空间? 

   •建议不要使用

安全版本:

 •char *strncpy(char *restrict dst,const char *restrist src,size_t n);

 •char *strcat(char *restrict s1, char *restrict s2,size_t n);//认为设定一个n,超过n的字符不进行复制粘贴

 •int strncmp(const *s1,const char *s2,size_t n);//只比较两字符串前n个字符

strchr字符串中找字符

  •char *strchr(const char *s,int c);//从左开始找,在s字符串中找到c字符第一次出现的位置

  •char *strrchr(const char *s,int c);//从右开始找,在s字符串中找到c字符第一次出现的位置

  •返回NULL表示没找到

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
BasicBasic BasicBasic正是微软公司售出的第一套件。 正是微软公司售出的第一套件。 Basic Basic Basic Basic 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 发展到今天已经有很多版本,如 GW -Basic Basic Basic Basic 、 QuickBasic QuickBasic QuickBasic QuickBasic QuickBasic QuickBasic QuickBasic 、QBasic QBasic QBasic QBasic QBasic 、Visual Basic Visual Basic Visual Basic Visual Basic Visual Basic Visual Basic Visual Basic Visual Basic , 等其中Visual Basic Visual Basic Visual Basic Visual Basic Visual Basic Visual Basic Visual Basic Visual Basic Visual Basic 是最容易学习与应用的程序语 是最容易学习与应用的程序语 是最容易学习与应用的程序语 是最容易学习与应用的程序语 是最容易学习与应用的程序语 是最容易学习与应用的程序语 是最容易学习与应用的程序语 言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来言之一。 虽然最容易学习与使用,但它的功能却非常强大很多应程序都是编写出来不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 不是个都能成为比尔盖茨,但你想知道天使用的 WindowsWindowsWindows Windows的诸多功能是如何实现吗? 的诸多功能是如何实现吗? 的诸多功能是如何实现吗? 的诸多功能是如何实现吗? 的诸多功能是如何实现吗? 的诸多功能是如何实现吗? 的诸多功能是如何实现吗? 你想要编写自己的应用程 你想要编写自己的应用程 你想要编写自己的应用程 你想要编写自己的应用程 你想要编写自己的应用程 序吗?通过学习 序吗?通过学习 序吗?通过学习 序吗?通过学习 VB 就能写出很多应用程序。 就能写出很多应用程序。 就能写出很多应用程序。 就能写出很多应用程序。 就

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

琛:D

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值