本文是基于中国MOOC平台上的《程序设计入门——C语言(翁恺)》课程,所作的一篇课程笔记,便于后期进行系统性查阅和复习。
本章的内容是提供一些背景知识,关于计算机、程序、编程语言,也关于C语言。是一篇导论,帮助学生建立关于计算机工作方式和编程语言的正确概念。
计算机如何解决问题?
需要一步步的告诉计算机如何解决问题。这里可以看到人与计算机的区别,对人(What to do),对计算机(How to do)。
计算机语言:
• 程序是用特殊的编程语言写出来表达如何解决问题的。
• 不是用编程语言来和计算机交谈,而是描述要求它如何做事情的过程或方法。
算法(计算机-程序-算法):
• 我们要让计算机做计算,就需要找出计算的步骤,然后用编程语言写出来。
• 计算机做的所有事情都叫做计算。
• 计算的步骤就是算法。
重复是计算机最擅长的。
程序的执行有两种方案:
1.解释:借助一个程序,那个程序能试图理解你的程序,然后按照你的要求执行。
2.编译:借助一个程序,就像一个翻译,把你的程序翻译成计算机能懂的语言(即机器语言),然后这个机器语言写的程序就能直接执行。
解释语言vs编译语言
• 语言本无解释/编译之分(任何一种语言都可以解释执行,又可以编译执行)。
• 区分只在于传统和习惯(比如C语言常用编译执行,python常用解释执行)。
• 解释型语言有特殊的计算能力。
• 编译型语言有确定的运算性能。
C语言在工业界有重要的地位,在很多领域无可替代,几乎所有和硬件打交道的地方都要用C语言。本课程中按照C99来展开教学,但国内很多大学和计算机二级考试仍坚持更老的ANSL C,所以在课程中,凡C99和ANSL C不同之处,均会进行额外标注。
• 现代的编程语言在语法上差异很小,其他语言几乎都是C-like语言。
• 语句的能力/适用领域主要是由库和传统所决定的。
C的版本-标准
• 1989年ANSI发布了一个标准——ANSI C
• 1990年ISO接受了ANSI的标准——C89
• C的标准在1995和1999两次更新——C95和C99
大学中往往会让初学者使用Dev C++来学习C语言,而对于有一定基础的朋友大多会使用Visual Studio。
#include
int main{
return 0;
}
•本课程中所有的程序都需要这一段
•直到学函数之前,我们的代码都只是在这个框架中间
•为了便于理解,目前不对程序框架进行解释,知道第一条即可
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;
C语言中赋值与初始化的差异并不大,但在其他语言,如C++这两种之间差异会比较大。
变量赋值
•price=0;
•这是个赋值语句,“=”是赋值运算符,表示将“=”右边的值赋给左边的变量。
变量初始化
•<数据类型名称><变量名称>=<初始值>;
•int price=0;
•int amount=100;
•组合变量定义的时候,也可以在这个定义中单独给单个变量赋初值,如:
•int price=0,amount=100;
变量类型
C语言是一种有类型的语言。所有变量在使用之前必须定义或声明,所有变量必须有具体的数据类型。数据类型表示在变量中可以存放什么样的数据,变量中只能存放指定类型的数据,程序运行过程中也不能改变变量的类型。
int change=100-price;
•100直接写在程序里,我们称作直接量。
•更好的方式,是定义一个常量:const int AMOUNT=100;(C99才有的用法)//便于理解、修改
const
•是一个修饰符,加在int的前面,用来给这个变量加一个const(不变的)的属性。这个const的属性表示这个变量的值一旦初始化,就不能再修改了。
10和10.0在C中是两个完全不同的数,10.0是浮点数。
浮点数
•C语言中,人们借助浮点数来表达所有的带小数点的数。
•当浮点数和证书放在一起运算时,C语言会将整数转换成浮点数,然后进行浮点数的运行。进而得到更精准的结果——浮点数。
表达式
•一个表达式是一系列运算符和算子的组合,用来计算一个值。
•运算符是指进行运算的动作,比如加法运算符“+”运算符、减法“-”运算符。
•算子是指参与运算的值,这个值可能是常熟,也可能是变量,还可能是一个方法的返回值。
运算符优先级
初等运算符>单目运算符>算术运算符(先乘除、后加减)>关系运算符>逻辑运算符(不含!)>条件运算符>赋值运算符>逗号运算符
以上的优先级,从左往右递减。
赋值运算符
•赋值也是运算,也有结果
•a=6的结果是a被赋予的值,也就是6
•a=b=6——>a=(b=6)
如何交换两个变量的值?
int a=6; int b=5; int c; c=a; a=b; b=c;
复合赋值
•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加1 a原来的值 a+1以后的值 ++a 给a加1 a+1以后的值 a+1以后的值 a-- 给a减1 a原来的值 a-1以后的值 --a 给a减1 a-1以后的值 a-1以后的值
本课程在该章仅对判断与循环语句进行了基础讲解,若想了解更高级的语句,请见第四章。
if(条件成立){
//语句1
}
条件
•计算两个值之间的关系,叫做关系运算。
•关系运算符的结果:当两个值的关系符合关系运算符的预期时,关系运算的结果为整数1,否则为整数0。
关系运算符 意义
== 相等 != 不相等 > 大于 >= 大于等于 < 小于 <= 小于等于
if(条件不成立){
//语句1
}else{
//语句2
}
if(条件不成立)
//语句1
else
//语句2
提示:这样进行编程是不好的习惯!if和else后面一定要用{}!
如果不使用{},if和else只能执行if和else后的单个语句,不便于执行多条语句的情况。使用{},能够明确标识出哪些语句属于if和else语句的范围,避免了可能的歧义,增加了代码的可读性和可维护性。
循环语句选择小tips:如果有固定次数,用for;
如果必须执行一次,用do-while;
其他情况,用while。
while循环基本语句:
while(循环条件){
//循环体
}
•while循环的意思:当条件满足时,不断重复循环体内的语句,直至条件不满足。
•循环执行之前判断是否继续循环,所以有可能循环一次也没有被执行
do-while循环基本语句:
do{
//循环体
}while(循环条件)
•先执行循环体,然后再检查循环条件是否成立,若成立再执行循环体。
for循环基本语句:
for(循环变量赋初值;循环条件;循环变量增值){
//循环语句
}
for语句能用于两种情况:1.循环次数已经确定。2.循环次数不确定而只给出循环结束条件。
使用方式:
•头文件#include
•之后就可以使用bool和true、false
#include
#include
int main(){
bool b=6>5; //b为true
bool t=false; //t为false
printf("%d %d",b,t);//输出1,0
return 0;
}
•逻辑运算是对逻辑量进行运算,结果只有0或1
•逻辑量是关系运算或逻辑运算的结果
运算符 | 描述 | 示例 | 结果 |
! | 逻辑非 | !a | 如果a是true,结果就是false; 如果a是false,结果就是true。 |
&& |
逻辑与 | a&&b | 如果a和b都是true,结果就是true,否则结果是false |
|| | 逻辑或 | a||b | 如果a和b都是false,结果就是false,否则结果是true |
条件运算符(表达式1?表达式2:表达式3)
•条件?条件满足时的值:条件不满足时的值
•条件运算符的优先级高于赋值运算符,但低于其他所有运算符
嵌套条件运算符
•条件运算符是自右向左结合的
•w
•嵌套条件运算符不利于理解、调试,不推荐使用!
逗号运算符
•逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。
•i=3+4,5+6;与i=(3+4,5+6);//i=7与i=11
•该运算符常用于for语句
找三个数的最大?
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);
分段函数?(单一出口比多出口的代码更优秀)
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;
switch-case基本语句:
switch(控制表达式) { case 常量: 语句; break;//注意break不要忘掉! default: 语句; break; }
编程的难点:把问题转换为程序。(变量,算法,流程图,程序)
算平均数
算法: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
#include 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
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”?
if语句常见错误
•忘了大括号,
•if后面错误加分号
•错误使用==和=
•令人迷惑的else
判断数是否是素数?
难点:1.判断整除——取余%为0,说明是整除。
2.仅输出一句话——加入一个新的变量来判断输出。
3.循环控制,在第一次整除就可以判断数不是素数了——break跳出循环体。
#include
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; }
写程序输出100以内的素数
#include
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; }
上述正是嵌套循环,需要注意在嵌套循环中内部和外部的循环变量应该不同,避免出现混淆。
如何从循环中跳出(break、continue、goto)
break:只能跳出一层循环体,如需跳出嵌套循环,可使用接力break。
continue:跳过循环体中剩余的语句而强制进入下一次循环,并没有跳出循环体。
goto:goto XX;XX:位于循环体外。//goto最好只用于跳出循环,不要用于其他情况
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);
输入两个数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
辗转相除法: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 );
输入一个非负整数,正序输出它的每一位数字
我的解答:
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博客
写程序计算用户输入数字的平均数,并输出所有大于平均数的数。
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]); } }
•<类型>变量名称[元素数量]
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];
•可以存在,但是无用
写程序,输入数量不确定的[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]); }
求和函数
#include
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; }
什么是函数?
•函数是一块代码,接受零个或多个参数,做一件事情,并返回零个或一个值
•可以想象成数学中的函数: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参数表
调用函数
•函数名(参数值)
•()起到了表示函数调用的重要作用
•即使没有参数也要()
从函数中返回值
•return停止函数的执行,并送回一个值
•return;
•return 表达式;//return会把表达式送回调用函数的位置
从函数中返回值
•该值可以赋值给变量
•该值可以再传递给函数
•该值也可以丢弃
没有返回值的函数
•void 函数名(参数表)
•不能使用带值的return
•可以没有return
•调用时不能做返回值的赋值
函数先后关系
前文把函数写在调用函数上面,是因为C的编译器自上而下顺序分析代码。
如果不将函数整体放在调用函数之前?
#include
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);
•函数原型的目的:告诉编译器这个函数长什么样(名称、参数数量及类型、返回类型)
•旧标准习惯把函数原型写在调用它的函数里面
•现在一般写在调用它的函数前面
调用函数
•如果函数有参数,调用函数必须传递给它的数量、类型正确的值
•可以传递给函数的值是表达式的结果,这包括字面量、变量、函数的返回值、计算的结果
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语言在调用函数时,永远只能传值给函数。
•每个变量有自己的变量空间,参数也位于这个独立的空间中,和其它函数没有关系。
•过去,对于函数参数表中的参数,叫做“形式参数”,调用函数给的值,叫做“实际参数”
(但这种说法容易对初学者造成误导,故不建议继续使用这种说法)
•我们认为,调用函数给的就是值,函数参数表中的参数就是参数,两者是值与参数的关系
本地变量
•函数的每次运行,就产生一个独立的变量空间,在这个空间中的变量,是函数这次运行所独有的,称作本地变量。
•定义在函数内部的变量就是本地变量。
•参数也是本地变量。
变量的生存期和作用域
•生存期:什么时候这个变量开始出现,到什么时候它消亡了。
•作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)。
•对于本地变量而言,这两个量都是:大括号内——块。
本地变量的规则
• 本地变量是定义在块内的。
•程序运行进入这个块之前,其中的变量不存在;离开这个块,其中的变量就消失了。
•本地变量不会被默认初始化。
没有参数时,如何写参数?
•void f(void)
•void f()//编译器会默认参数未知,而非没有参数
•建议使用void f(void);
调用函数中的逗号和逗号运算符怎么区分?
•f(a,b);//这里的逗号就是单纯的标点符号
•f(a,b);//这里的逗号就是逗号运算符
可以在函数里面定义另一个函数吗?
•不可以,C语言不允许嵌套定义
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}, };
•列数是必须给出的,行数可以由编译器来数
•每行一个{},逗号分隔
•最后的逗号可以存在,有古老的传统
•如果省略,表示补零
数组的集成初始化
int a[23]={0};
数组的大小
•sizeof()
搜索
•在一个数组中找到某个数的位置(或确认是否存在)
•基本方法:遍历
#include
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; }
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]);
指针
•就是保存地址的变量
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;
传入函数的数组成了什么?
•函数参数表的数组实际上是指针
•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的指针,所以不能被赋值。
•char是一种整数,也是一种特殊的类型:字符。这是因为:
•用单引号表示的字符字面量:'a'、'1'。
•''也是一个字符
•printf和scanf中用%c来输入输出字符
ASCII表
•字母在其中顺序排列
•大写字母和小写字母分开排列,并不在一起
char c='A'; c++;//ASCII码加一,c='B' printf("%c\n",c);//输出B
字符 | 意义 | 字符 | 意义 |
\b | 回退一格 | \" | 双引号 |
\t | 到下一个表格位 | \' | 单引号 |
\n |
换行 | \\ | 反斜杠本身 |
\r | 回车 |
字符数组
•char world[]={'H','e','l','l','o','!'};
word[0] word[1] word[2] word[3] word[4] word[5] H e l l o ! •这不是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] H e l l o ! \0
字符串变量
•char *str="Hello";//一个叫str的指针,它指向了一个字符数组,这里面放的内容是Hello
char word[]= "Hello";//这有个叫word的字符数组,它里面的内容是Hello
char line[10]="Hello";//这有个叫line的字符数组,大小有十个字节那么大,向里面放入Hello
"Hello"
•"Hello"会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表结束的0
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,才能说它所指的是字符串
字符串赋值
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
头文件#include
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表示没找到