本人在练习 OJ 的过程中有些感想,觉得有本 C 语言学习笔记很重要,故通过复习和代码实战整理了这篇《C 语言学习笔记》,内容比较齐全,在逐渐完善和补充中,如果在使用中出现任何疑问或者有更好的见解的话,可以右下角 OPEN CHAT 我,也可以右上角 邮我,当然还可以加入我的讨论组,如果觉得本书对你有帮助,可以打赏我,以鼓励我更好的创作,下面附微信支付二维码,再次谢谢您的大力支持!
如果觉得文章有帮助,不妨请我喝杯 Coffee,祝福好心人年年高升!
下面正式开始:
我们通过 "语言" 来控制计算机,让计算机为我们做事情,这样的语言就叫做编程语言(Programming Language)。
编程语言有很多种,常用的有 C 语言、C++、Java、C#、Python、PHP、JavaScript、Go 语言、Objective-C、Swift、汇编语言等,每种语言都有自己擅长的方面,例如:
编程语言 | 主要用途 |
---|---|
C/C++ | C++ 是在 C 语言的基础上发展起来的,C++ 包含了 C 语言的所有内容,C 语言是 C++ 的一个部分,它们往往混合在一起使用,所以统称为 C/C++。C/C++ 主要用于 PC 软件开发、Linux 开发、游戏开发、单片机和嵌入式系统。 |
Java | Java 是一门通用型的语言,可以用于网站后台开发、Android 开发、PC 软件开发,近年来又涉足了大数据领域(归功于 Hadoop 框架的流行)。 |
C# | C# 是微软开发的用来对抗 Java 的一门语言,实现机制和 Java 类似,不过 C# 显然失败了,目前主要用于 Windows 平台的软件开发,以及少量的网站后台开发。 |
Python | Python 也是一门通用型的语言,主要用于系统运维、网站后台开发、数据分析、人工智能、云计算等领域,近年来势头强劲,增长非常快。 |
PHP | PHP 是一门专用型的语言,主要用来开发网站后台程序。 |
JavaScript | JavaScript 最初只能用于网站前端开发,而且是前端开发的唯一语言,没有可替代性。近年来由于 Node.js 的流行,JavaScript 在网站后台开发中也占有了一席之地,并且在迅速增长。 |
Go 语言 | Go 语言是 2009 年由 Google 发布的一款编程语言,成长非常迅速,在国内外已经有大量的应用。Go 语言主要用于服务器端的编程,对 C/C++、Java 都形成了不小的挑战。 |
Objective-C Swift | Objective-C 和 Swift 都只能用于苹果产品的开发,包括 Mac、MacBook、iPhone、iPad、iWatch 等。 |
汇编语言 | 汇编语言是计算机发展初期的一门语言,它的执行效率非常高,但是开发效率非常低,所以在常见的应用程序开发中不会使用汇编语言,只有在对效率和实时性要求极高的关键模块才会考虑汇编语言,例如操作系统内核、驱动、仪器仪表、工业控制等。 |
可以将不同的编程语言比喻成各国语言,为了表达同一个意思,可能使用不同的语句。例如,表达“世界你好”的意思:
- 汉语:世界你好;
- 英语:Hello World
- 法语:Bonjour tout le monde
在编程语言中,同样的操作也可能使用不同的语句。例如,在屏幕上显示“Hello World”:
- C 语言:puts("Hello World");
- PHP:echo "Hello World";
- Java:System.out.println("Hello World");
这是一个没有答案的问题。每个人投入的时间、学习效率和基础都不一样。如果你每天都拿出大把的时间来学习,那么很快就能学会。程序员被戏称为”码农“,意思是写代码的农民,要想成为一个合格的农民,必须要脚踏实地辛苦耕耘。
在学习其他语言之前,个人建议先学习 C 语言,触类旁通,讲究的是思考问题的方式和思考的内容,后期会学习算法和数据结构,当然会有 C 版本、C++ 版本以及 Java 版本等等,你可能都会涉及到,但是不一定全都要看,前提是算法思想懂了!
好的,现在开始投入 C 语言的怀抱中来,推荐 IDE 编辑器:Dev-C++,当然你有其他更好的选择也可以咯!
程序框架如下:
#include <stdio.h>
int main()
{
return 0;
}
一个 C 语言程序的结构有以下特点:
(1)一个程序由一个或者多个源程序文件组成
(2)在一个源程序文件中包括3部分:预处理指令、全局声明、函数定义
(3)函数是 C 语言程序的主要组成部分
(4)一个函数包括两个部分:函数首部和函数体,函数体又包括声明部分和执行部分
(5)程序总是从 main 函数开始之执行的
(6)程序中对计算机的操作是由函数中的 C 语句完成的
(7)在每个数据声明和语句的最后必须有一个分号
(8)C 语言本身不提供输入输出语句
(9)程序应当包含注释
人生第一个代码:HelloWorld,很经典的哟!哈哈
#include <stdio.h>
int main()
{ //<---函数体的开始
// 输出 HelloWorld! <----这是注释
printf("HelloWorld!\n");//<---函数调用语句
return 0;//<---返回语句
}
- 命令行编辑、编译、运行程序
Example 01:
ls -l
gcc hello.c
ls -l
./a.out
Example 02:(默认在64位电脑上执行)
gcc hello.c -o hello
./hello
Example 03:(-m32 表示在32位电脑上执行)
gcc hello.c -o hello -m32
./hello
注释内容可以用英文或者汉字
-
- 单行注释://注释内容
以//的单行注释,以换行符结束
-
- 多行注释:/* 注释内容 */
这种注释可以跨越多行
1.常量
在程序运行过程中,其值不能被改变的量称为常量
常量有以下几类:
(1)整型常量:如 1000,12345,0,-234 等
(2)实型常量:十进制小数形式、指数形式
(3)字符常量:① 普通字符,用单撇号括起来的一个字符,如 'a', 'Z' 等 ② 转义字符,如 ''','\' 等
(4)字符串常量:用双引号引起来的多个字符,如 "China" 等
(5)符号常量:用 #define 指令,指定用一个符号名称代表一个常量,如:
#define PI 3.1416
符号常量的优点:含义清楚、一改全改
2.变量
变量代表一个有名字的、具有特定属性的一个存储单元,它用来存放数据,也就是存放变量的值。在程序运行期间,变量的值是可以改变的。
变量必须先定义,后使用。
3.常变量
C99 允许使用常变量:
const int AMOUNT = 100;
常变量是有名字的不变量,而常量是没有名字的不变量。
常变量和符号常量有什么不同?
答:定义符号常量用 #define 指令,它是预编译指令,它知识用符号常量代表一个字符串,在预编译时仅是进行字符替换,在预编译后,符号常量就不存在了,对符号常量的名字是不分配存储单元的。而常变量要占用存储单元,有变量值,只是该值不改变。
4.标识符
标识符就是一个对象的名字。如变量名、函数名等等
C语言规定标识符只能由字母、数字和下划线 3 种字符组成,且第一个字符必须为字母或下划线。
(1)int 型
-
编译系统分配给 int 型数据 2 个字节或 4 个字节(VC++ 6.0 就是分配 4 个字节)。
-
在存储单元中的存储方式:用整数的补码形式存放。
-
int 表示一个寄存器的大小
(2)short int 型
- 分配 2 个字节
(3)long int 型
- 分配 4 个字节,在一个整数的末尾加大写字母 L 或小写字母 l 即可表示为 long int 型
(4)long long int 型
- 分配 8 个字节
(5)字符 char 类型
- 分配 1 个字节
(6)float 类型(单精度浮点型)
- 分配 4 个字节
(7)double 类型(双精度浮点型)
- 分配 8 个字节
(8)long double 类型(长双精度浮点型)
- Turbo C 分配给 long double 16 个字节
- Visual C++ 6.0 分配 8 个字节
auto | break | case | char | const |
---|---|---|---|---|
continue | default | do | double | else |
enum | extern | float | for | goto |
if | int | long | register | return |
short | signed | sizeof | static | struct |
switch | typedef | union | unsigned | void |
volatile | while | inline | restrict |
- 变量初始化
- <类型名称><变量名称> = <初始值>;
- eg. int price = 0;
eg.
int a = b + 5;
- 计算时间差
#include "stdio.h"
/*计算时间差*/
int main()
{
int hour1, minute1;
int hour2, minute2;
scanf("%d %d", &hour1, &minute1);
scanf("%d %d", &hour2, &minute2);
int t1 = hour1 * 60 + minute1;
int t2 = hour2 * 60 + minute2;
int t = t2 - t1;
printf("时间差是 %d 小时 %d 分钟。",t/60, t%60);
return 0;
}
- 运算符优先级
自增、自减运算符:
++i 、--i :使用 i 之前,先使 i 的值加(减)1
i++ 、i-- :在使用i之后,使 i 的值加(减)1
自增、自减运算符只能用于变量,而不能用于常量或表达式。
强制类型转换运算符:
(类型名)(表达式)
求字节数运算符:
sizeof
scanf (格式输入)、printf (格式输出)
getchar (输入字符)、putchar (输出字符)
gets (输入字符串)、puts (输出字符串)
在使用它们之前需要在开头用预处理指令 #include <stdio.h>
printf 函数的一般格式
printf (格式控制,输出列表)
例如:printf (" %f 约等于 %d ", i , c );
1 类型安全
- 强类型
- 早期语言强调类型,面向底层的语言强调类型
- C语言需要类型,但是对类型的安全检查并不足够
2 sizeof
- 是一个静态运算符,给出某个类型或者变量在内存中所占据的字节数
- sizeof(int)
- sizeof(i)
- Example 01:
#include <stdio.h>
int main()
{
int a = 6;
printf("sizeof(int)=%ld\n",sizeof(int));
printf("sizeof(a)=%ld\n",sizeof(a));
return 0;
}
3 补码
- Example 01:
#include <stdio.h>
int main()
{
char c = 255;
int i = 255;
printf("c=%d,i=%d\n",c,i);
return 0;
}
4 unsigned
- 无符号整数型(0-255)
- 255u
- 用 l 或 L 表示 long
- unsigned 的初衷并非是扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位
-
只有两种形式:int 或 long long
-
%d:int
-
%u:unsigned
-
%ld:long long
-
%lu:unsigned long long
-
Example 01:
#include <stdio.h>
int main()
{
char c = -1;
int i = -1;
printf("c=%u,i=%u\n",c,i);
return 0;
}
- Example 02:八进制和十六进制转换为十进制
#include <stdio.h>
int main()
{
char c = 012;//八进制
int i = 0x12;//十六进制
printf("c=%d,i=%d\n",c,i);
return 0;
}
- Example 03:八进制和十六进制输出
#include <stdio.h>
int main()
{
char c = 012;//八进制
int i = 0x12;//十六进制
printf("c=0%o,i=0x%x\n",c,i);
printf("c=0%o,i=0x%X\n",c,i);
return 0;
}
-
float (4字节) 输入 %f 格式,输出 %f 或 %e
-
double (8字节)输入 %lf 格式,输出 %lf 或 %e
-
科学计数法:-5.67E+16
-
输出精度:
- 在 % 和 f 之间加上 .n 可以指定输出小数点后几位,这样的输出是做4舍5入的
- printf("%.3f\n",-0.0046);
#include <stdio.h>
int main()
{
printf("%.3f\n",-0.0046);
printf("%.30f\n",-0.0046);
printf("%.3f\n",-0.00046);
return 0;
}
-
超过范围的浮点数:
- printf 输出 inf 表示超过范围的浮点数:±∞
- printf 输出 nan 表示不存在的浮点数
-
Example 01:
#include <stdio.h>
int main()
{
printf("%f\n",12.0/0.0);
printf("%f\n",-12.0/0.0);
printf("%f\n",0.0/0.0);
return 0;
}
- 浮点运算的精度
- f1 == f2 可能值相等,但不等
- 利用 fabs(f1-f2)<1e-12
-
如何输入 ‘1‘ 这个字符给 char c?
-
scanf("%c",&c);
-
#include <stdio.h> int main() { char c; scanf("%c",&c); printf("c=%d\n",c); printf("c=%c\n",c); return 0; }
-
-
Example 01:
#include <stdio.h>
int main()
{
char c;
char d;
c = 1;
d = '1';
if(c == d){
printf("相等\n");
}else{
printf("不相等\n");
}
printf("c=%d\n",c);
printf("d=%d\n",d);
return 0;
}
- 字母大小写转换
- a+'a'-'A' 大写字母变成小写字母
- a+'A'-'a' 小写字母变成大写字母
5 逃逸字符
字符 | 意义 | 字符 | 意义 |
---|---|---|---|
\b | 回退一格 | \ " | 双引号 |
\t | 到下一个表格位 | \ ' | 单引号 |
\n | 换行 | \ \ | 反斜杠本身 |
\r | 回车 |
- Example 01:
#include <stdio.h>
int main()
{
printf("123\bA\n456");
return 0;
}
6 自动类型转换
- 当运算符的两边出现不一致的类型时,会自动转换成较大的类型
- char --> short --> int --> long --> long long
- int --> float --> double
- short ----- %hd
- long ------ %ld
7 强制类型转换
-
Example 01:
-
(int)10.2
-
(short)32
-
反例1:
-
#include <stdio.h> int main() { printf("%d\n",(short)32768); return 0; }
-
结果:-32768
-
反例2:
-
#include <stdio.h> int main() { printf("%d\n",(char)32768); return 0; }
-
结果:0
注:
强制类型转换的优先级高于四则运算
C 语言有两种选择语句:
(1)if 语句,用来实现两个分支的选择结构
if (表达式)
语句1
else
语句2
(2)switch 语句,用来实现多分支的选择结构
switch(表达式)
{
case 常量1 :语句1
case 常量2 :语句2
...
case 常量n :语句n
default: 语句n+1
(1)用 while 语句实现循环
while(表达式)
语句
(2)用 do...while 语句实现循环
do
语句
while(表达式);
(3)用 for 语句实现循环
for(表达式1;表达式2;表达式3)
语句
- 改变循环执行的状态
(1)用 break 语句提前终止循环
一般形式:break;
注意:break 语句还可以用来从循环体内跳出循环体,即提前结束循环,接着执行循环下面的语句。break 语句只能用于循环语句和 switch 语句之中,而不能单独使用。
(2)用 continue 语句提前结束本次循环
一般形式:continue;
注:作用为结束本次循环,即跳过循环体中下面尚未执行的语句,转到循环体结束点之前,然后进行下一次是否执行循环的判定。
bool
- #include <stdbool.h>
- 之后就可以使用bool和true、false
#include <stdio.h>
#include <stdbool.h>
int main()
{
bool b = 6>5;
printf("%d",b);
return 0;
}
随机数
- rand()
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//主函数
int main(){
srand(time(0));
int a = rand();
printf("%d\n",a);
return 0;
}
- x%n 的结果是[0,n-1]的一个整数
printf("%d\n",a%100);
-
Example:猜数游戏
-
Example 01:
#include <stdio.h>
int main()
{
unsigned char c = 255;
int i = 255;
printf("c=%d,i=%d\n",c,i);
return 0;
}
逻辑运算
运算符 | 描述 | 事例 |
---|---|---|
! | 逻辑非 | !a |
&& | 逻辑与 | a && b |
|| | 逻辑或 | a || b |
-
函数是一块代码,接收零个或多个参数,做一件事情,并返回零个或一个值
-
函数原型,以分号结尾,即声明
-
Example 01:判断素数
int isPrime(int i){ int result = 1; int k; for(k=2;k<i-1;k++){ if(i%k == 0){ result = 0; break; } } return result; }
-
Example 02:求和函数 ==> 求1到10、20到30和36到45的三个和
-
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); }
-
C 语言在调用函数时,永远只能传值给函数
局部变量
- 也叫:本地变量 ==> 定义在函数内部的变量
- 生存期和作用域 ==> 大括号内,即代码块
全局变量
- 定义在函数体外面
避免代码复制
代码复制是代码不良的表现!
数组大小
- const int number = 10;
- 使用 sizeof 给出整个数组所占据的内容的大小,单位是字节:
sizeof(a)/sizeof(a[0])
初始化数组
-
for(i=0;i<number;i++){ count[i]=0; }
集成初始化
int a[] = {2,4,6,7,1};
- 集成初始化时的定位
int a[10] = {[0] = 2,[2] = 3,6,};
定义数组
-
< 类型 > 变量名称 [ 元素数量 ]
-
int number[100];
-
scanf("%d",&number[i]);
-
int grades[100];
-
double weight[20];
-
元素数量必须是整数
-
使用数组时放在[]中的数字叫做下标或索引,下标从0开始计数
-
有效的下标范围
- [ 0 , 数组的大小 - 1 ]
数组的赋值
注:数组变量本身不能被赋值,要把一个数组的所有元素交给另一个数组,必须采用遍历
for(i=0;i<length;i++){
b[i] = a[i];
}
数组运算
遍历数组输出
for(i=0;i<number;i++){
printf("%d:%d\n",i,count[i]);
}
- int a [ 3 ] [ 5 ] 理解为 a 是一个 3 行 5 列的矩阵
初始化
int a[][5] = {
{0,1,2,3,4},
{2,3,4,5,6},
}
注:列数必须给出
数组运算
指针
- 就是保存地址的变量
- %p 用来输出指针的值、输出地址符,以16进制的形式输出内存地址
int i;
int* p = &i;
int* p,q;
int *p,q;
int *p,*q;
指针变量
- 指针变量的值是具有实际值的变量的地址
void f(int *p);
int i=0;
f(&i);
- Example 01:
#include <stdio.h>
void f(int *p);
int main(){
int i = 6;
printf("&i=%p\n",&i);
f(&i);
return 0;
}
void f(int *p){
printf("p=%p\n",p);
}
指针应用场景
- 场景一:交换两个变量的值 [ swap(&a,&b) ]
void swap(int *pa,int *pb){
int t = *pa;
*pa = *pb;
*pb = t;
}
- 场景二:求最大、最小值
void minmax(int a[],int len,int *min,int *max){
int i;
*min = *max = a[0];
for(i=1;i<len;i++){
if(a[i]<*min){
*min = a[i];
}
if(a[i]>*max){
*max = a[i];
}
}
}
- 场景三:函数返回运算的状态,结果通过指针返回
#include <stdio.h>
int divide(int a,int b,int *result);
int main(){
int a = 5;
int b = 2;
int c;
if(divide(a,b,&c)){
printf("%d/%d=%d\n",a,b,c);
}
return 0;
}
int divide(int a,int b,int *result){
int ret = 1;
if(b == 0){
ret = 0;
}else{
*result = a/b;
}
}
注:数组变量是特殊的指针
- 数组变量本身表达地址,所以
- int a[10];int *p = a; // 无需用&取地址
- a == &a[0]
指针计算
- Example 01:
#include <stdio.h>
int main(){
char ac[] = {0,1,2,3,4,5,6,7,8,9,};
char *p = &ac[0];
char *p1 = &ac[5];
printf("p=%p\n",p);
printf("p+1=%p\n",p+1);
printf("p1-p=%d\n",p1-p);
return 0;
}
*p++
-
*的优先级虽然高,但是没有++高
-
取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
-
Example 02:
#include <stdio.h>
int main(){
char ac[] = {0,1,2,3,4,5,6,7,8,9,-1};
char *p = &ac[0];
int i;
for(i=0;i<sizeof(ac)/sizeof(ac[0]);i++){
printf("%d\n",ac[i]);
}
while(*p != -1){
printf("%d\n",*p++);
}
return 0;
}
0地址
- 内存中有0地址,但是不能随便碰
- 0地址用来表示特殊的事情:
- 返回的指针是无效的
- 指针没有被真正初始化(先初始化为0)
- NULL是一个预定定义的符号,表示0地址
动态内存分配
输入数据
-
int * a = (int *)malloc(n * sizeof(int))
-
向 malloc 申请的空间的大小是以字节为单位的,返回的是void * ,需要类型转换为自己需要的类型
-
Example 01:
#include <stdio.h>
#include <stdlib.h>
int main(){
int number;
int *a;
int i;
printf("输入数量:");
scanf("%d",&number);
a = (int *)malloc(number*sizeof(int));
for(i=0;i<number;i++){
scanf("%d",&a[i]);
}
for(i=number-1;i>=0;i--){
printf("%d ",a[i]);
}
free(a);
return 0;
}
字符数组:char word[] = {'H','e','l','l','o','!'};
字符串:char word[] = {'H','e','l','l','o','!','\0'};
- 字符串以0结尾的一串字符,以数组的形式存在,以数组或指针的形式访问
-
char * str = "Hello";
-
char word[] = "Hello";
-
char line[10] = "Hello";
-
Example 01:
#include <stdio.h>
int main(){
char *s = "Hello World\0";
char s1[] = "Hello World\0";
s1[0] = 'B';
printf("s=%s\n",s);
printf("s1=%s\n",s1);
printf("Here!s1[0]=%c\n",s1[0]);
return 0;
}
- scanf 读入一个单词(到空格、tab或回车为止)
char string[8];
scanf("%s",string);
printf("%s",string);
- char buffer[100] = ""; //这是一个空的字符串,buffer[0] == '\0';
- char buffer[] = ""; //这个数组的长度只有1
字符串数组
putchar
- int putchar(int c);
- 向标准输出写一个字符
- 返回写了几个字符,EOF(-1)表示写失败
getchar
- int getchar(void)
- 从标准输入读入一个字符
- 返回类型时int是为了返回EOF(-1)
- windows-----ctrl+Z
- linux-----------ctrl+D
案例:
- Example 01:
#include <stdio.h>
int main(){
int ch;
while((ch = getchar()) != EOF){
putchar(ch);
}
printf("EOF\n");
return 0;
}
在使用字符串处理函数时,应当在程序文件的开头用 #include <string.h> 把 ”string.h“ 文件包含到本文件中,
string.h
标准库中包含函数:
strlen
-
测字符串长度的函数
-
size_t strlen(const char *s);
-
返回s的字符串长度(不包括结尾的0)
-
Example 01:
#include <stdio.h>
#include <string.h>
int main(){
char line[] = "Hello";
printf("strlen=%lu\n",strlen(line));
printf("sizeof=%lu\n",sizeof(line));
return 0;
}
- Example 02:用mylen自定义函数,替代库中strlen
#include <stdio.h>
#include <string.h>
int mylen(const char *s){
int idx = 0;
while(s[idx]!='\0'){
idx++;
}
return idx;
}
int main(){
char line[] = "Hello";
printf("strlen=%lu\n",mylen(line));
printf("sizeof=%lu\n",sizeof(line));
return 0;
}
strcmp
-
字符串比较函数
-
int strcmp(const char *s1,const char *s2);
-
比较两个字符串,返回:
- 0:s1 == s2
- 1:s1 > s2
- -1:s1 < s2
-
Example 01:
#include <stdio.h>
#include <string.h>
int main(){
char s1[] = "abc";
char s2[] = "abc";
printf("%d\n",strcmp(s1,s2));
return 0;
}
- Example 02:
#include <stdio.h>
#include <string.h>
int main(){
char s1[] = "abc";
char s2[] = "Abc";
printf("%d\n",strcmp(s1,s2));
printf("%d\n",'a'-'A');
return 0;
}
- Example 03:
#include <stdio.h>
#include <string.h>
int mycmp(const char *s1,const char *s2){
int idx = 0;
while(s1[idx] == s2[idx] && s1[idx] != '\0'){
// if(s1[idx] != s2[idx]){
// break;
// }else if(s1[idx] == '\0'){
// break;
// }
idx++;
}
return s1[idx] - s2[idx];
}
int main(){
char s1[] = "abc";
char s2[] = "Abc";
printf("%d\n",mycmp(s1,s2));
printf("%d\n",'a'-'A');
return 0;
}
- Example 04:
#include <stdio.h>
#include <string.h>
int mycmp(const char *s1,const char *s2){
while(*s1 == *s2 && *s1 != '\0'){
s1++;
s2++;
}
return *s1 - *s2;
}
int main(){
char s1[] = "abc";
char s2[] = "Abc";
printf("%d\n",mycmp(s1,s2));
printf("%d\n",'a'-'A');
return 0;
}
strcpy
- char *strcpy(char *restrict dst, const char *restrict src);
- 把src的字符串拷贝到dst
- restrict表明src和dst不重叠(C99)
- 返回dst,为了能链起代码
复制一个字符串
//动态申请内存
char *dst = (char*)malloc(strlen(src)+1);
//拷贝src到dst
strcpy(dst,src);
- Example 01:自定义版本----数组
#include <stdio.h>
#include <string.h>
int mycpy(char *dst,char *src){
int idx = 0;
while(src[idx]){
dst[idx] = src[idx];
idx++;
}
//dst[idx] = src[idx];
dst[idx] = '\0';
return dst;
}
int main(){
char s1[] = "abc";
char s2[] = "Abc";
printf("%s\n",strcpy(s1,s2));
return 0;
}
- Example 02:自定义版本----指针
#include <stdio.h>
#include <string.h>
int mycpy(char *dst,char *src){
char *ret = dst;
// while(*src){
//// *dst = *src;
//// dst++;
//// src++;
// *dst++ = *src++;
// }
while(*dst++ = *src++){
}
*dst = '\0';
return ret;
}
int main(){
char s1[] = "abc";
char s2[] = "Abc";
printf("%s\n",strcpy(s1,s2));
return 0;
}
strncpy
-
将一个字符串2中前面n个字符复制字符串2中,但复制的字符个数不多于str1中原有的字符(不包括'\0')
-
strncpy(str1,str2,2);
-
Example 01:
#include <stdio.h>
#include <string.h>
int mycpy(char *dst,char *src){
int idx = 0;
while(src[idx]){
dst[idx] = src[idx];
idx++;
}
//dst[idx] = src[idx];
dst[idx] = '\0';
return dst;
}
int main(){
char s1[] = "abc";
char s2[] = "ACd";
printf("%s\n",strncpy(s1,s2,2));
return 0;
}
strcat
-
字符串连接函数
-
char *strcat(char *restrict s1, const char *restrict s2);
-
把s2拷贝到s1的后面,接成一个长的字符串
-
返回s1
-
s1必须具有足够的空间
-
Example 01:
#include <stdio.h>
int main(){
char str1[30] = {"I am "};
char str2[] = {"a senior."};
puts(strcat(str1,str2));
return 0;
}
注: 用 puts 和 gets 函数只能输出或输入一个字符串,不能写成 puts(str1,str2); gets(str1,str2);
strchr
-
在字符串中找字符
-
char * strchr(const char *s,int c);
-
返回NULL表示没有找到
-
Example 01:
#include <stdio.h>
#include <string.h>
int main(){
char s[] = "hello";
char *p = strchr(s,'l');
p = strchr(p+1,'l');
printf("%s\n",p);
return 0;
}
- Example 02:
#include <stdio.h>
#include <string.h>
int main(){
char s[] = "hello";
char *p = strchr(s,'l');
char *t = (char*)malloc(strlen(p)+1);
strcpy(t,p);
printf("%s\n",t);
free(t);
return 0;
}
- Example 03:
#include <stdio.h>
#include <string.h>
int main(){
char s[] = "hello";
char *p = strchr(s,'l');
char c = *p;
*p = '\0';
char *t = (char*)malloc(strlen(s)+1);
strcpy(t,s);
printf("%s\n",t);
free(t);
return 0;
}
strrchr
- Example 01:
#include <stdio.h>
#include <string.h>
int main(){
char s[] = "hello";
char *p = strchr(s,'l');
p = strrchr(p,'l');
printf("%s\n",p);
return 0;
}
strstr
- 字符串中找字符串
- char *strstr(const char *s1, const char *s2);
- char *strcasestr(const char *s1, const char *s2);
strlwr
- 将字符串中大写字母转换为小写字母函数
- Example 01:
#include <stdio.h>
int main(){
char c[]="ABCDefG";
printf("%s\n",strlwr(c));
return 0;
}
strupr
-
将字符串中小写字母转换为大写字母函数
-
Example 01:
#include <stdio.h>
int main(){
char c[]="ABCDefG";
printf("%s\n",strupr(c));
return 0;
}
安全问题
- 要考虑
用枚举优化常量符号化,变得更加方便
-
enum 枚举类型名字{名字0,...,名字n};
-
Example 01:
#include <stdio.h>
enum color{
red,
yellow,
green
};
void f(enum color c);
int main(){
enum color t = red;
scanf("%d",&t);
f(t);
return 0;
}
void f(enum color c){
printf("%d\n",c);
}
枚举量
-
声明枚举量的时候可以指定值
-
enum color{ red = 1, yellow, green = 5 };
枚举只是int
实际上很少用
声明结构类型
- Example 01:
#include <stdio.h>
struct date{
int month;
int day;
int year;
};
int main(){
struct date today;
today.month = 03;
today.day = 05;
today.year = 2020;
printf("%i-%i-%i",today.year,today.month,today.day);
//%i表示有符号十进制整数
return 0;
}
声明结构的形式
- 形式一:
struct point{
int x;
int y;
};
struct point p1,p2; // p1和p2都是point里面有x和y的值
- 形式二:
struct{
int x;
int y;
}p1,p2;// p1和p2都是一种无名结构,里面有x和y
- 形式三:(推荐)
struct point{
int x;
int y;
}p1,p2;
// p1和p2都是point里面有x和y的值
结构变量
struct date today;
today.month = 03;
today.day = 05;
today.year = 2020;
结构体的初始化
struct date today = {03,05,2020};
结构成员
today.day
结构运算
-
赋值、取地址、传递函数参数
-
p1 = (struct point){5,10}; //相当于p1.x = 5,p1.y = 10;
-
p1 = p2; //相当于p1.x = p2.x; p1.y = p2.y;
结构可作为函数参数
bool isLeap(struct date d);
结构指针作为参数
- 指向结构的指针
struct date{
int month;
int day;
int year;
}myday;
struct date *p = &myday;
(*p).month = 12;
p -> month = 12;
用->表示指针所指的结构变量中的成员
结构数组
struct date dates[100];
struct date dates[] = {{4,5,2005},{2,4,2005}};
结构中的结构
struct dateAndTime{
struct date sdate;
struct time stime;
}
- Example 01:
Typedef
- typedef 自定义数据类型
-
typedef struct{ int month; int day; int year; }Date;
union AnElt{
int i;
char c;
}elt1,elt2;
elt1.i = 4;
elt2.c = 'a';
- sizeof(union ...) = // sizeof (每个成员) 的最大值
#include <stdio.h>
typedef union{
int i;
char ch[sizeof(int)];
}Data;
int main(){
Data data;
int i;
data.i = 1234;
for(i=0;i<sizeof(int);i++){
//对于%02hhx,hhx已经以一个字节打印了,加上02限制,不够两位的补成两位。
printf("%02hhX",data.ch[i]);
}
printf("\n");
return 0;
}
全局变量初始化
- 默认初始化为0,指针会得到NULL
- 尽量不要使用全局变量来在函数间传递参数和结果
-
static
-
使用全局变量和静态本地变量的函数是线程不安全的
编译预处理指令
-
#开头的是编译预处理指令
-
它们不是c语言的成分,但是c语言程序离不开它们
-
#define 用来定义一个宏,原始的文本替换
-
Example 01:
-
#include <stdio.h> //const double PI = 3.14159; #define PI 3.14159 int main(){ printf("%f\n",2*PI); return 0; }
-
-
Example 02:
-
#include <stdio.h> //const double PI = 3.14159; #define PI 3.14159 #define PI2 2*PI // PI * 2 #define PRT printf("%f ",PI); \ printf("%f\n",PI2) int main(){ // printf("%f\n",PI); // printf("%f\n",PI2); PRT; return 0; }
-
#define
- 有值的宏:参考上面 Example 02
- 没有值的宏:#define _DEBUG // 这类宏适用于条件编译,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了
预定义的宏
-
_ _ LINE _ _
-
_ _ FILE _ _
-
_ _ DATE _ _
-
_ _ TIME _ _
-
_ _ STDC _ _
-
Example 01:
-
#include <stdio.h> int main(){ printf("%s:%d\n",__FILE__,__LINE__); printf("%s:%s\n",__DATE__,__LINE__); return 0; }
-
像函数的宏
-
#define cube(x) ( (x) * (x) * (x) )
-
宏可以带参数
-
Example 01:
-
#include <stdio.h> #define cube(x) ((x)*(x)*(x)) int main(int argc,char const *argv[]){ int i; scanf("%d",&i); printf("%d\n",cube(i)); return 0; }
-
-
带参数的宏的原则:
- 一切都要括号
- 整个值要括号
- 参数出现的每个地方都要括号
- #define RADTODEG(x) ( (x) * 57.29578 )
- 一切都要括号
-
宏可以带多个参数:
- #define MIN(a,b) ((a)>(b)?(b):(a))
-
宏也可以组合(嵌套)使用其他宏
多个 .c 文件
- 在 Dev C++ 中新建一个项目,然后把几个源代码文件加入进去,然后编译和构建运行即可。
-
编译单元
- 一个 .c 文件是一个编译单元
- 编译器每次编译只处理一个编译单元
-
引入头文件
- 把函数原型放在一个头文件(以 .h 结尾)中,在需要调用这个函数的源代码文件( .c 文件)中 #include 这个头文件,就可以让编译器在编译的时候知道函数的原型。
声明
在 .h 中添加 extern int gAll; 就能使用 gAll 了
-
int i; //是变量的定义
-
extern int i; //是变量的声明
-
声明不产生代码,定义产生代码
-
声明包括:
- 函数原型
- 变量声明
- 结构声明
- 宏声明
- 枚举声明
- 类型声明
- inline 声明
-
只有声明才能放在头文件中
标准头文件结构
-
条件编译指令
-
#ifndef _ MAX_H _ //如果没有定义
-
#define _ MAX_H _ //则定义
-
#endif
-
避免了重复引用的情况
- linux 用 > 和 < 做重定向
-
打开文件的标准代码:
-
FILE *fp = fopen("file","r"); if(fp){ fscanf(fp,...); fclose(fp); }else{ ... }
-
Example 01:
-
#include <stdio.h> int main(int argc,char const *argv[]){ FILE *fp = fopen("12.in","r"); if(fp){ int num; fscanf(fp,"%d",&num); printf("%d\n",num); fclose(fp); } else{ printf("无法打开文件\n"); } return 0; }
-
fopen
- 其实所有的文件最终都是二进制的
- 文本文件读写:
- more、tail
- cat
- vi
- 二进制文件需要专门的程序来读写
- 文本文件的输入输出是格式化,可能经过转码
文件练习举例:
-
Example 01:
-
student.h
-
#ifndef _STUDENT_H_ #define _STUDENT_H_ //const int STR_LEN = 20; #define STR_LEN 20 typedef struct _student{ char name[STR_LEN]; int gender; int age; }Student; #endif
-
main.c
-
#include <stdio.h> #include "student.h" void getList(Student aStu[],int number); int save(Student aStu[],int number); int main(int argc, char *argv[]) { int number = 0; printf("输入学生数量:"); scanf("%d",&number); Student aStu[number]; getList(aStu,number); if(save(aStu,number)){ printf("保存成功\n"); }else{ printf("保存失败\n"); } return 0; } void getList(Student aStu[],int number){ char format[STR_LEN]; //向字符串输出 sprintf(format,"%%%ds",STR_LEN-1); //%19s int i; for(i=0;i<number;i++){ printf("第%d个学生:\n",i); printf("\t姓名:"); scanf(format,aStu[i].name); printf("\t性别(0-男,1-女,2-其他):"); scanf("%d",&aStu[i].gender); printf("\t年龄:"); scanf("%d",&aStu[i].age); } } int save(Student aStu[],int number){ int ret = -1; FILE *fp = fopen("student.data","w"); if(fp){ ret = fwrite(aStu,sizeof(Student),number,fp); fclose(fp); } return ret == number; }
-
-
测试结果:
-
Example 02:
-
read.c
-
#include <stdio.h> #include "student.h" void read(FILE *fp,int index); int main(){ FILE *fp = fopen("student.data","r"); if(fp){ fseek(fp,0L,SEEK_END); long size = ftell(fp); int number = size / sizeof(Student); int index = 0; printf("有%d个数据,你要看第几个:",number); scanf("%d",&index); read(fp,index-1); fclose(fp); } return 0; } void read(FILE *fp,int index){ fseek(fp,index * sizeof(Student),SEEK_SET); Student stu; if(fread(&stu,sizeof(Student),1,fp) == 1){ printf("第%d个学生:",index+1); printf("\t姓名:%s\n",stu.name); printf("\t性别:"); switch(stu.gender) { case 0:printf("男\n");break; case 1:printf("女\n");break; case 2:printf("其他\n");break; } printf("\t年龄:%d\n",stu.age); } }
-
-
Test Result:
- 可移植性
- C 有这些按位运算的运算符:
- & 按位与
- | 按位或
- ~ 按位取反
- ^ 按位异或
- << 左移
>>
右移
-
按位运算输出 int
-
左移
- 右移
注:移位的位数不要用负数,这是没有定义的行为
-
Example 01:
-
array.h
-
#ifndef _ARRAY_H_ #define _ARRAY_H_ typedef struct { int *array; int size; }Array; #define BLOCK_SIZE 20 Array array_create(int init_size); void array_free(Array *a); int array_size(const Array *a); int *array_at(Array *a,int index); void array_inflate(Array *a,int more_size); int array_get(const Array *a,int index); void array_set(Array *a,int index,int value); #endif
-
array.c
-
#include "array.h" #include <stdio.h> #include <stdlib.h> //typedef struct { // int *array; // int size; //}Array; Array array_create(int init_size){ Array a; a.size = init_size; a.array = (int *)malloc(sizeof(int)*a.size); return a; } void array_free(Array *a){ free(a->array); a->array = NULL; a->size = 0; } int array_size(const Array *a){ return a->size; } int *array_at(Array *a,int index){ if(index>=a->size){ //array_inflate(a,index-a->size+1); array_inflate(a,(index/BLOCK_SIZE +1)*BLOCK_SIZE-a->size); } return &(a->array[index]); } //可变字符自动按块增长 void array_inflate(Array *a,int more_size){ int *p = (int *)malloc(sizeof(int)*(a->size + more_size)); int i; for(i=0;i<a->size;i++){ p[i] = a->array[i]; } free(a->array); a->array = p; a->size += more_size; } int array_get(const Array *a,int index){ return a->array[index]; } void array_set(Array *a,int index,int value){ a->array[index] = value; } int main(){ Array a = array_create(100); printf("%d\n",array_size(&a)); printf("%d\n",a.size); *array_at(&a,0) = 10; printf("%d\n",*array_at(&a,0)); int number; int cnt = 0; while(number != -1){ scanf("%d",&number); if(number!=-1){ *array_at(&a,cnt++) = number; } } array_free(&a); return 0; }
-
可变数组的缺陷
- 要 copy,不能充分利用
链表存储数据 add
- Example 01:
#include <stdio.h>
#include <stdlib.h>
typedef struct _node{
int value;
struct _node *next;
}Node;
int main(){
Node *head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1){
// add to linkList
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the last
Node *last = head;
if(last){
while(last->next){
last = last->next;
}
// attach
last->next = p;
}else{
head = p;
}
}
} while(number != -1);
return 0;
}
- Example 02:对 01 进行改进
#include <stdio.h>
#include <stdlib.h>
typedef struct _node{
int value;
struct _node *next;
}Node;
Node* add(Node *head, int number);
int main(){
Node *head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1){
head = add(head,number);
}
} while(number != -1);
return 0;
}
Node* add(Node *head, int number){
// add to linkList
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the last
Node *last = head;
if(last){
while(last->next){
last = last->next;
}
// attach
last->next = p;
}else{
head = p;
}
return head;
}
- Example 02:对 01 进行改进
#include <stdio.h>
#include <stdlib.h>
typedef struct _node{
int value;
struct _node *next;
}Node;
Node* add(Node **pHead, int number);
int main(){
Node *head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1){
head = add(&head,number);
}
} while(number != -1);
return 0;
}
Node* add(Node **pHead, int number){
// add to linkList
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the last
Node *last = *pHead;
if(last){
while(last->next){
last = last->next;
}
// attach
last->next = p;
}else{
*pHead = p;
}
return *pHead;
}
- Example 03:对 02 进行改进
#include <stdio.h>
#include <stdlib.h>
typedef struct _node{
int value;
struct _node *next;
}Node;
typedef struct _list{
Node *head;
}List;
void add(List *pList, int number);
int main(){
List list;
list.head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1){
add(&list,number);
}
} while(number != -1);
return 0;
}
void add(List *pList, int number){
// add to linkList
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the last
Node *last = pList->head;
if(last){
while(last->next){
last = last->next;
}
// attach
last->next = p;
}else{
pList->head = p;
}
}
- Example 04:对 03 进行改进
typedef struct _node{
int value;
struct _node *next;
}Node;
typedef struct _list{
Node *head;
Node *tail;
}List;
void add(List *pList, int number);
int main(){
List list;
list.head = list.tail = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1){
add(&list,number);
}
} while(number != -1);
return 0;
}
待完善:list.tail
链表输出数据 print
- Example 01:
#include <stdio.h>
#include <stdlib.h>
typedef struct _node{
int value;
struct _node *next;
}Node;
typedef struct _list{
Node *head;
Node *tail;
}List;
void add(List *pList, int number);
int main(){
List list;
list.head = list.tail = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1){
add(&list,number);
}
} while(number != -1);
Node *p;
//遍历输出
for(p=list.head;p;p=p->next){
printf("%d\t",p->value);
}
printf("\n");
return 0;
}
void add(List *pList, int number){
// add to linkList
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the last
Node *last = pList->head;
if(last){
while(last->next){
last = last->next;
}
// attach
last->next = p;
}else{
pList->head = p;
}
}
- Example 02:对 01 进行优化
#include <stdio.h>
#include <stdlib.h>
typedef struct _node{
int value;
struct _node *next;
}Node;
typedef struct _list{
Node *head;
//Node *tail;
}List;
void add(List *pList, int number);
void print(List *pList);
int main(){
List list;
list.head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1){
add(&list,number);
}
} while(number != -1);
print(&list);
return 0;
}
void add(List *pList, int number){
// add to linkList
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the last
Node *last = pList->head;
if(last){
while(last->next){
last = last->next;
}
// attach
last->next = p;
}else{
pList->head = p;
}
}
void print(List *pList){
Node *p;
//遍历输出
for(p=pList->head;p;p=p->next){
printf("%d\t",p->value);
}
printf("\n");
}
- Test Result
链表查找数据并删除
- Example 01:
#include <stdio.h>
#include <stdlib.h>
typedef struct _node{
int value;
struct _node *next;
}Node;
typedef struct _list{
Node *head;
//Node *tail;
}List;
void add(List *pList, int number);
void print(List *pList);
int main(){
List list;
list.head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1){
add(&list,number);
}
} while(number != -1);
print(&list);
//查找数据
scanf("%d",&number);
Node *p;
int isFound = 0;
for(p=list.head;p;p=p->next){
if(p->value == number){
printf("找到了\n");
isFound = 1;
break;
}
}
if(!isFound){
printf("没有找到\n");
}
//删除某个数据
Node *q;
for(q=NULL,p=list.head;p;q=p,p=p->next){
if(p->value == number){
if(q){
q->next = p->next;
}else{
list.head = p->next;
}
free(p);
break;
}
}
//删除所有数据
for(p=list.head;p;p=q){
q = p->next;
free(p);
}
return 0;
}
void add(List *pList, int number){
// add to linkList
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the last
Node *last = pList->head;
if(last){
while(last->next){
last = last->next;
}
// attach
last->next = p;
}else{
pList->head = p;
}
}
void print(List *pList){
Node *p;
//遍历输出
for(p=pList->head;p;p=p->next){
printf("%d\t",p->value);
}
printf("\n");
}