- 这是一篇有关C语言语法基础的精炼概述性质文章,不会涉及C语言一些有深度的内容
- 本文面向的是以大学C考试过关为目标,以计算机二级认证为目标,期望获得一个C语言最基础认识的读者
- 博主能力有限,可能某些知识点未能讲解清晰,某些知识点遗漏,甚至某些知识点错误,若发现以上问题欢迎邮件勘误。
1
2
3
4
5
6
7
| #include<stdio.h>
int main(){
/* 这是每个程序的开始 */
printf("Hello World!\n"); //这是另一种注释方法
return 0;
}
|
所有C语言程序都包含一个main()函数,程序从main()函数开始执行,每个函数用花括号"{}"包括。
C语言有两种添加注释方法
- 以“//”开头的单行注释
- 以“/**/”包裹的多行注释,或者叫块注释
stdio.h是一个头文件(标准输入输出头文件,这个头文件内包含printf函数的定义与实现,如果没有找到stdio.h,printf函数会出现编译错误)。
#include 是一个预处理命令(c语言中以#开头的命令称为预处理命令,类似还有#define)用来引入头文件。
return 0; 用于退出程序,并向操作系统返回一个数值0.
每一句完整的c语言代码用分号";"(英文小写)作为结束符号
百度百科
维基百科
重点
- C语言是一门面向过程的高级语言
- C语言仍然保持跨平台特性
- 未完待续。。。
| 类型 | 存储大小 | 取值范围 |
|---|
| char | 1 byte | -2^7~2^7-1 |
| int | 2 byte(32bit) 4 Byte(64bit) | -2^152^15-1 -2^312^31-1 |
| short | 2 byte | -2^15~2^15-1 |
| long | 4 byte | -2^31~2^31-1 |
结合之前对存储单位的科普。我们知道1 Byte=8bit即1字节等于8个比特位。计算机内部其实只认识1和0,也就是二进制。所以计算机对任何数据的处理都是转换成对应的二进制。1字节对应8个二进制位即:
用8个位子随意组合摆放0,1可以由2^8种组合,计算机内部将这2^8种组合在一一映射到实际的数值上。列如:
1
2
3
4
5
6
7
| 00000000 --> -2^7=-128
00000001 --> -2^7+1=-127
···
10000000 --> 0
10000001 --> 1
···
11111111 --> 2^7-1=127
|
所以我们就有了
1
| char 1 Byte(字节)范围 -2^7~2^7 即 -128~127
|
其他类型也可以同样的方法计算范围。也正如表中所给的数值范围,全部以2的次幂记忆。如:
1
2
3
4
5
6
| int 在64位下 大小为4Byte(字节)
4 Byte=4*8 bit=32bit
也就是32个二进制bit位
所以int型的范围可以表示为
-2^31~2^31-1
(有没有奇怪为森魔要-1?因为数字0的存在)
|
值得一提的是c语言种对基本数据类型还有两个修饰符可以使用:
1
2
| unsigned 中文释义:无符号
signed 中文释义:有符号
|
顾名思义,举例来说,如果我们使用unsigned修饰int型
1
2
| unsigned char x;
/* 如此声明的这个变量x的取值范围就会变成 0~2^8-1 即 0-255 */
|
而signed有符号,以上常用数据类型默认都是由signed(有符号修饰的)所以他们的范围从负数开始,如果你加上unsigned(无符号)那么所有的数据类型范围将是从0开始
| 类型 | 存储大小 | 取值范围 | 精度 |
|---|
| float | 4 Byte | 1.2E-38 ~ 3.4E+38 | 6位小数 |
| double | 8 Byte | 2.3E-308 ~ 1.7E+308 | 15位小数 |
| long double | 16 Byte | 3.4E-4932 ~ 1.1E+4932 | 19位小数 |
- 有关浮点型的相关问题属于较有深度的问题,这里不做探究
void类型呢是一个比较特殊的类型。
1
2
3
4
5
6
7
8
| #include<stdio.h>
void main(){
printf("hello world");
return;
}
/*
还记得return吗,用于结束函数并向操作系统或者上级调用函数返回一个数值。如果这个函数是用void修饰的,列如这里的main()函数,这里return就不必返回一个数值(如果你还是写作return 0;还会报错)
*/
|
1
2
3
4
5
6
7
8
9
10
| #include<stdio.h>
int func(void){
printf("hallo world");
return 0;
}
int main(){
func();
return 0;
}
//其实默认括号内为空就表示没有参数,即void是可以省略的
|
- 用来修饰指针代表对象地址,而不是一个类型。(关于指针会在指针部分详解)
1
2
3
4
5
6
7
8
| //所有基本数据类型均可以定义对应常量
#define N 100
#define Good true
#define x 1.2345
#define newline '\n'
//你也可以使用那个const关键字定义常量
const double x=0.123456;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #include<stdio.h>
int main(){
int i; //声明整形变量i
int x,y; //声明整形变量x,y
float a;
double b;
char ch; //声明字符变量ch
scanf("%d",&i); //从键盘输入数字i
scanf("%d %d",&x,&y); //从键盘同时输入数字x,y
scanf("%f %lf",&a,&b);
scanf("%c",&ch); //从键盘读入一个字符ch
printf("hallo world\n"); //打印固定字符串
printf("%d\n",i); //打印单个数字
printf("你输入的x=%d,y=%d\n",x,y);//格式化输出
printf("单精度a=%f;双精度b=%.3lf",a,b);//打印输出浮点类型,并限制位数
printf("%c",ch);//打印单个字符
return 0;
}
|
1
2
3
| 10
2 3
1.234 3.141592654
|
1
2
3
4
| hallo world
10
你输入的x=2,y=3
单精度a=1.234000;双精度b=3.142
|
- 你可能会奇怪不是应该还输入一个单个字符吗,为什么没有了,其实那个单个字符就是两个小数最后的回车符所以你也看到了我的输出其实多了一行,对那就是那个最后输入的回车符。
- "%.3f"这个写法是限制小数后打印三位,默认四舍五入
| 格式输出符号 | 含义 |
|---|
| %a | 浮点数、十六进制数字和p-记数法(c99 |
| %A | 浮点数、十六进制数字和p-记法(c99) |
| %c | 一个字符(char) |
| %C | 一个ISO宽字符 |
| %d | 有符号十进制整数(int)(%ld、%Ld:长整型数据(long),%hd:输出短整形。) |
| %e | 浮点数、e-记数法 |
| %E | 浮点数、E-记数法 |
| %f | 单精度浮点数(默认float)、十进制记数法(%.nf 这里n表示精确到小数位后n位.十进制计数) |
| %g | 根据数值不同自动选择%f或%e. |
| %G | 根据数值不同自动选择%f或%e. |
| %i | 有符号十进制数(与%d相同) |
| %o | 无符号八进制整数 |
| %p | 指针 |
| %s | 对应字符串char*(%s = %hs = %hS 输出 窄字符) |
| %S | 对应宽字符串WCAHR*(%ws = %S 输出宽字符串) |
| %u | 无符号十进制整数(unsigned int) |
| %x | 使用十六进制数字0xf的无符号十六进制整数 |
| %X | 使用十六进制数字0xf的无符号十六进制整数 |
| %% | 打印一个百分号 |
| %I64d | 用于INT64 或者 long long |
| %I64u | 用于UINT64 或者 unsigned long long |
| %I64x | 用于64位16进制数据 |
基本常用的就是整数,小数,字符,字符串的输出。
转义字符
| 转义字符 | 含义 | ASCII码值 |
|---|
| \a | 响铃(BEL) | 007 |
| \b | 退格(BS) ,将当前位置移到前一列 | 008 |
| \f | 换页(FF),将当前位置移到下页开头 | 012 |
| \n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
| \r | 回车(CR) ,将当前位置移到本行开头 | 013 |
| \t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
| \v | 垂直制表(VT) | 011 |
| \ | 代表一个反斜线字符''' | 092 |
| ' | 代表一个单引号(撇号)字符 | 039 |
| " | 代表一个双引号字符 | 034 |
| \0 | 空字符(NULL) | 000 |
| \ddd | 1到3位八进制数所代表的任意字符 | 三位八进制 |
| \xhh | 1到2位十六进制所代表的任意字符 | 二位十六进制 |
难点
scanf()函数,以空格或者回车符作为截断。
C语言中的输入输出缓冲区机制
在C/C++中,输入输出事实上是各自有一个缓冲区的。缓冲区故名思意。
- 在你的键盘,屏幕和程序实际获得输入之间还有一个缓冲区。
- 你按下的按键会被先存放到缓冲区内,接着程序从输入缓冲区读取。处理完毕之后将输出写在输出缓冲区内,屏幕再从输出缓冲区内读取输出并显示给你。
并不是你想象中的你按下的每一个字符都会直接被程序接收
当再输入的时候涉及到了字符,或者字符串,并且你发现输入的数据并没有按照你的需要,按照你所想的进行,多半是因为在输入缓冲区内有你之前输入操作时遗留的字符在里面(简单举例,你使用scanf输入一个数字并且按下回车,数字被程序从输入缓冲区读取,但是遗留下来了一个回车符,如果你紧接着读取一个字符,就会出现你意料之外的情况,也就是上述输入输出示例代码中的情况。)
当输入缓冲区出现问题时可以使用如下代码清除缓冲区:
然而并不是所有的编译器支持这个函数
这里假设A=10,B=20
| 运算符 | 含义 | 实列 |
|---|
| + | 把两个操作数相加 | A + B 将得到 30 |
| - | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
| * | 把两个操作数相乘 | A * B 将得到 200 |
| / | 分子除以分母 | B / A 将得到 2 |
| % | 取模运算符,整除后的余数 | B % A 将得到 0 |
| ++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
| -- | 自减运算符,整数值减少 1 | A-- 将得到 9 |
自增自减运算符的理解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
| #include<stdio.h>
int main(){
int x=0;
printf("%d\n",x++);
printf("%d\n",x);
printf("%d\n",--x);
printf("%d\n",x);
return 0;
}
```
样例输出
```text
0
1
0
0
```
* 当这两个运算符出现在程序语句中时
* 如果位于变量前,则先执行自增(或自减)再执行程序语句
* 如果位于变量之后,则先执行程序语句,再进行自增(或自减)
除此之外c语言还支持如下的写法简化:
```c
i=i+1; 可以写作 i+=1;
i=i*10; 可以写作 i*=10;
i=i/2; 可以写作 i/=2;
i=i-5; 可以写作 i-=5;
```
---
### 关系运算符
这里假设A=10,B=20
| 运算符 | 描述 | 实例 |
| :----- | :------------------------------------------------------------- | :---------------- |
| == | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真。 |
| != | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
| > | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 |
| < | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
| >= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 |
| <= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
---
### 逻辑运算符
这里假设A=10,B=20
| 运算符 | 描述 | 实例 |
| :----- | :--------------------------------------------------------------------------------- | :--------------- |
| && | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
| \|\| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A \|\| B) 为真。 |
| ! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
---
### 按位运算符
位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:
| p | q | p & q | p \| q | p ^ q |
| --- | --- | ----- | ------ | ----- |
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 | 1 |
假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:
```text
A = 0011 1100
B = 0000 1101
-----------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011
```
* c语言中实际的位运算符
| 位运算符 | 描述 | 实列 |
| :------- | :------------------------------------------------------------------------------------------ | :--------------------------------------------------------------- |
| & | 按位与操作,按二进制位进行"与"运算。 | (A & B) 将得到 12,即为 0000 1100 |
| \| | 按位或运算符,按二进制位进行"或"运算。 | (A \| B) 将得到 61,即为 0011 1101 |
| ^ | 异或运算符,按二进制位进行"异或"运算。 | (A ^ B) 将得到 49,即为 0011 0001 |
| ~ | 取反运算符,按二进制位进行"取反"运算。 | (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 |
| << | 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 | A << 2 将得到 240,即为 1111 0000 |
| >> | 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 | A >> 2 将得到 15,即为 0000 1111 |
---
## 条件选择语句
* **c语言把任意非零,非空的值定义为true,把零或null定义为false**
### 基本if语句
```c
#include<stdio.h>
int main(){
if (1) printf("hallo ");
if (true) {
printf("world");
}
int x=10;
if (x) printf("\n");
//求a,b,c种的最大值
int a=5,b=2,c=3;
int ans;
if (a>b){
ans=a;
}
else{
if (b>c){
ans=b;
}
else{
ans=c;
}
}
printf("a=%d,b=%d,c=%d\nMax=%d\n",a,b,c,ans);
// &&且 ||或
if (a>b && b>c) printf("a\n");
if (a>b || b>c) printf("b\n");
return 0;
}
|
输出如下
1
2
3
4
| hallo world
a=5,b=2,c=3
Max=5
b
|
- 在用&&连接的两个表达式A,B中。如果A的运算值为false,那么表达式B不会运算
- 在用||连接的两个表达式A,B中。如果A的运算值为true,那么表达式B不会运算
1
2
3
4
5
6
7
8
9
10
11
12
13
| #include<stdio.h>
int main(){
int a=1,b=2,c=3;
if (a>b && ++a) printf("Yes\n");
//这里由于a>b不成立,所以a++不会被执行,所以a的值仍然为1,当然prinft语句也不会被执行
if (a<b && ++a) printf("%d\n",a);
//这里由于a<b成立,所以a++也会被执行,这里会运行输出a数值2
if (b<c || ++b) printf("%d\n",b);
// ||也是如此,由于b<c成立,于是b++也不会被执行,输出b的数值为2
return 0;
}
|
样例输出
1
2
3
4
5
6
7
8
| #include<stdio.h>
int main(){
int n;
printf("请输入一个数字:");
scanf("%d",&n);
(n>0)?printf("正数\n"):printf("负数\n");
return 0;
}
|
样例输入
样例输出
1
2
3
4
5
6
| if (n>0) {
printf("正数");
}
else {
printf("负数");
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
| #include<stdio.h>
int main(){
int n;
printf("请输入一个数字:");
scanf("%d",&n);
switch (n){
case 1: printf("this is case 1\n");
case 2: printf("this is case 2\n"); break;
default:
printf("this is default\n");
}
return 0;
}
|
样例输入1
样例输出1
1
2
| this is case 1
this is case 2
|
样例输入2
样例输出2
样例输入3
样例输出3
1
2
3
4
5
6
7
8
9
10
11
| #include<stdio.h>
int main(){
// 统计1加到100的和
int i;
int ans=0;
for (i=0;i<=100;i++){
ans=ans+i;
}
printf("%d\n",ans);
return 0;
}
|
样例输出
下面是 for 循环的控制流:
1
2
3
4
| for ( init; condition; increment )
{
statement(s);
}
|
- init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。
- 接下来,会判断 condition。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。
- 在执行完 for 循环主体后,控制流会跳回上面的 increment 语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。
- 条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for 循环终止。
在VC 6.0中不支持在init中声明循环控制变量,只允许使用上面的样例代码,否则会有
1
| [Error] 'for' loop initial declarations are only allowed in C99 or C11 mode
|
错误,在比较新版本的编译器中,如下写法是被支持的
1
2
3
| for (int i=0;i<=100;i++) {
ans=ans+1;
}//并且,这里的i变量在循环体结束之后就会被释放销毁,即你在循环体之外是无法再次使用i变量的。
|
1
2
3
4
5
6
7
8
9
10
| //求1~100内奇数的和
#include<stdio.h>
int main(){
int i=1;
int ans=0;
while (i<=100){
ans+=i;
i+=2;
}
}
|
执行逻辑
1
2
3
4
| while(condition)
{
statement(s);
}
|
1
2
3
4
5
6
7
8
9
10
| //计算100以内偶数的和
#include<stido.h>
int main(){
int i=2;
int ans=0;
do{
ans+=i;
i+=2;
}while(i<=100)
}
|
执行逻辑
1
2
3
4
5
| do
{
statement(s);
}while( condition );
|
1
2
3
4
5
6
7
8
9
10
11
| 返回值类型 函数名(参数类型 参数1,参数类型 参数2 ···){
函数主体
}
实列:
//求a,b中的最大值,并返回该最大值
int Max(int a,int b){
int ret;
(a>=b)?ret=a:ret=b;//还记的这个三元运算符吗?
return ret;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| #include<stdio.h>
int Max(int a,int b) {
int ret;
(a>=b)?ret=a:ret=b;
return ret;
}
int main() {
int a=10,b=20;
printf("%d\n",Max(a,b));
return 0;
}
|
样例输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #include<stdio.h>
//函数作用:交换ab的值,这是交换两个数常用写法。
void swap(int a,int b){
int t;
t=a;
a=b;
b=t;
}
int main(){
int a=10,b=20;
printf("a=%d,b=%d\n",a,b);
swap(a,b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
|
样例输出
1
2
3
4
5
6
7
8
9
10
11
12
13
| #include<stdio.h>
void swap(int *a,int *b){
int t=*a;
*a=*b;
*b=t;
}
int main(){
int a=10,b=20;
printf("a=%d,b=%d\n",a,b);
swap(&a,&b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
|
样例输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| #include<stdio.h>
//全局变量
int g=10;
void func1(int g){
printf("in func1 g=%d\n",g);
return;
}
void func2(){
printf("in func2 g=%d\n",g);
return;
}
int main(){
//局部变量
int x=20,g=30;
printf("in main x=%d,g=%d\n",x,g);
func1(x);//我们把x=20的值赋值给形式参数g
func2();
return 0;
}
|
样例输出
1
2
3
| in main x=20,g=30
in func1 g=20
in func2 g=10
|
- 在main()函数中尝试访问x,g的值并输出显示,程序优先查看局部变量中是否存在x,g,存在于是输出
- 因为局部变量声明了一个g,所以这里并不会访问全局的g
- 在func1()中我们尝试访问变量g,因为变量g在函数参数部分存在声明,是一个形式参数。所以也不会去访问全局变量g。于是输出
- 即传入的x的值。
- 在func2()中,我们没有在函数内部声明任何局部变量,形式参数,于是这里尝试访问变量g就是访问的全局变量的值,输出
重点
全局变量被保存在内存的全局存储区中,占用静态存储空间,并且会被初始化。
局部变量被保存在一种称为栈的结构中,只有在函数被调用的时候才会真正的被分配存储空间
局部变量被定义的时候,系统不会对其进行初始化(虽然多数情况下不初始化数值也多为0)
全局变量系统会自动进行初始化,初始化值如下
| 数据类型 | 初始化默认值 |
|---|
| int | 0 |
| char | '\0' |
| float | 0 |
| double | 0 |
| pointer(指针) | NULL |
示例:
1
2
3
4
| int a[100];
/*
这个实例里我们就定义了一个长度为100的数组,可以存储100个int型数据
*/
|
注意:C语言中的数组下标从0开始,这里我们定义了一个长度为100的数组a,但是其下标范围事实上是从0到99
1
2
3
4
5
6
7
8
9
| int a[10]={0};
//声明一个大小为10的整形数组,并初始化所有元素为0
//还记得吗,下标从0开始哦,0~9
double a[]={0.0,1.1,2.2,3.3}
//你也可以为数组里每个元素单独初始化,如果你这么做的话还可以省略方括号里的数组大小(这个数组大小为4)
int a[10]={0,1,2,3}
//如果规定了数组大小但是没有把每个元素单独赋值,未赋值部分依据上面提到的默认初始化规则初始化。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #include<stdio.h>
int main(){
//声明一个大小为100的int型数组并初始化所有值为0
int a[100]={0};
//将数组所有元素赋值为1~100
for (int i=0;i<100;i++){
//我们通过 数组名[下标] 的方式访问数组元素
a[i]=i+1;
}
//我们计算数组里所有元素的累加和,即1-100的累加和
int ans=0;
for (int i=0;i<100;i++){
ans+=a[i];
}
//样例输出5050
printf("%d\n",ans);
return 0;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| #include<stdio.h>
//计算a数组内所有元素的平均值
double func(int* a,int size){
double average=0;
for (int i=0;i<size;i++)
average+=a[i];
return average/(double)size;
}
/*
其实你可以发现数组传参的方法就是一个"引用传参"
所以数组使用这种方法进行函数传参是可以修改数组元素的
数组函数传参还有别的写法,但是这里只讲解这一种。
希望你养成一个好习惯,数组传参务必带上数组大小
*/
int main(){
//声明初始化一个数组a
int a[5]={1,2,3,4,5};
//打印平均值
printf("%lf\n",func(a,5));
return 0;
}
|
数组的本质(重点)
数组本质是一个在内存中线性连续的存储空间
你在声明数组的时候事实上获得的是一个这个空间的地址
数组下标就是相对于数组地址的一个偏移量用来确定内部元素的位置
C语言中你要时刻注意数组下标,因为下标越界有时是不会报错的。
你声明了一个
的数组,你可以使用如下的循环来遍历它
1
2
3
4
5
6
7
| #include<stdio.h>
int main(){
int a[10]={0};
for(int i=0;i<=10;i++)
printf("%d ",a[i]);
return 0;
}
|
样例输出
我设置了循环条件是i<=10,但是你还记得吗?c语言数组下标从0开始!!!
所以这里输出了11个数字。你也发现了,我明明初始化了所有的数组元素为0,但是最后却多输出了一个1。这里就是因为我们越界访问。
最后这个数字1不属于数组的元素,但是你依旧可以访问它。
所以每当你尝试访问数组的时候,请千万注意数组下标越界问题。因为不检查数组下标越界已经在计算机界造成了许许多多的重大安全问题。
1
2
3
4
5
6
7
8
9
10
| /*
还记的\0吗,截止符
字符数组内存储的字符串的末尾必须有截止符!
*/
char name[6]={'h', 'e', 'l', 'l', 'o', '\0'};
//你还可以用这个写法.
char greeting[]="hello";
//初始化部分元素也是可以滴
char name[20]="xiaoming";
|
因为字符串数组末尾必定有截止符的特性,我们有了一些常用的遍历字符数组的方法:
1
2
3
4
5
6
7
8
9
10
| char str[]="hello world";
for (int i=0;str[i]!=0;i++){
···
}
int i=0;
while(str[i]!=0){
···
i++;
}
|
当然你也可以获取字符串长度,然后设定下标i小于等于字符串长度的时候执行循环,不过那样就和普通数组没有区别音次不在此举例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| char str[100];
//输入字符串
gets(str);
scanf("%s",&str);
//输出字符串
puts(str);
printf("%s\n",str);
/*
之前说到在数组处理,越界并不会被报错
所以这里的输入都存在着极大的安全隐患,于是在Visual studio的最新版本里,出于安全考虑
在默认情况下不允许使用以上函数读取字符串。下面提到的C语言字符串处理函数也是一样,都存在了越界问题
因而在最新的VS中默认不允许使用,这也是我不推荐使用最新版本的VS的原因
*/
|
| 函数 | 作用 |
|---|
| strcpy(s1,s2); | 复制字符串s2到字符串s1 |
| strcat(s1,s2); | 连接字符串s2到s1末尾 |
| strlen(s); | 获取字符串s长度 |
| strcmp(s1,s2); | 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。(这里的s1 s2基于字符数组内的字符的字典序判定) |
| strchr(s1,ch); | 返回一个指针,只想字符串s1中字符ch第一次出现的位置 |
| strstr(s1,s2); | 返回一个指针,指出字符串s1在字符串s2中年第一次出现的位置 |
sizeof()函数是可以对任何变量使用的,但是strlen()是字符串专属函数
1
2
3
4
5
6
7
8
| #include<stdio.h>
#include<string.h>
int main(){
char name[]="xiaoming";
printf("size of name=%d\n",sizeof(name));
printf("strlen of name=%d\n",strlen(name));
return 0;
}
|
样例输出
1
2
| size of name=9
strlen of name=8
|
1
2
3
4
5
6
7
8
| #include<stdio.h>
#include<string.h>
int main(){
char name[20]="xiaoming";
printf("size of name=%d\n",sizeof(name));
printf("strlen of name=%d\n",strlen(name));
return 0;
}
|
样例输出
1
2
| size of name=20
strlen of name=8
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| #include<stdio.h>
int main(){
//首先定义一个结构体DATE来存储出生年月
struct DATE{
short year; //年
short month;//月
short day; //日
};
//定义学生信息结构体StudentInfo
struct StudentInfo{
char name[20]={0}; //姓名
DATE date; //出生年月,对!结构体也可以嵌套定义
};
//对,我们要存储一个班的学生,50人
StudentInfo student[50];
for (int i=0;i<50;i++){
printf("输入第%d个学生信息:\n",i+1);
//我们使用结构体名称加"."的方式来访问结构体内部元素
printf("学生姓名:"); scanf("%s",&student[i].name);
printf("学生出生年月日:");
scanf(
"%d-%d-%d",
&student[i].date.year,
&student[i].date.month,
&student[i].date.day
);
};
for (int i=0;i<50;i++){
printf("%s\n",student[i].name);
printf(
"%d-%d-%d",
student[i].date.year,
student[i].date.month,
student[i].date.day
);
};
return 0;
}
|
样例输入
1
2
3
| 输入第1个学生信息:
学生姓名:xiaoming
学生出生年月日:1992-12-21
|
样例输出
1
2
3
4
5
| struct 结构体类型名{
基本数据类型 变量名;
基本数据类型 变量名;
...
}
|
- 结构体的声明,其实只是定义了一个由用户个人定义的一种数据类型,我们真正要使用的时候还需要重新真正声明一个结构体实际变量.正如上面代码例子中给出的那样。
- 除了上述的声明结构体实际变量的写法,我们还有如下几种其他方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| //如此我们在声明一个DATE结构体类型的同时,声明了一个date的结构体实体变量
stuct DATE{
int year;
int month;
int day;
}date;
//如果我们并不打算在别的地方使用这种结构体类型,只是单纯的为了声明这样一个结构体实体变量,还可以省略结构体类型名称
struct{
int year;
int month;
}date1;
//这个还是定义一个结构体类型DATE
typedef stuct{
int year;
int month;
}DATE;
|
这是本垃圾博主很少使用的一种结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| enum WEEK{
Monday=1,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
WEEK day;
/*
你也可以使用这种写法多写一个单词
enum WEEK day;
当然也可以像结构体一样声明枚举类型,到实体化枚举变量一气呵成
enum WEEK{
Monday=1,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
} day;
*/
|
- 第一个枚举类型成员值默认为整形0,后续枚举成员默认值为前一个成员+1.
1
| enum season {spring, summer=3, autumn, winter};
|
- 没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5
1
2
3
4
5
| int *p0;
short *p1;
long *p2;
char *p3;
float *p4;
|
1
2
3
4
| struct TEST{
int a,b;
};
TEST* p0;
|
- 我们也可以声明一个结构体类型,并且声明一个指向该结构体类型的指针变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| #include<stdio.h>
int main(){
int *p=NULL; //声明指针变量,并初始化为NULL(即0)NULL是C语言里约定的空指针值
int a; //声明一个正常整形变量
a=1; //将a赋值为1
p=&a; //将a的地址赋值给指针变量
//& 符号,取变量的地址
printf("a=%d\np=%p\n*p=%d",a,p,*p);
a=22; //改变a的值
printf("\na=%d\np=%p\n*p=%d",a,p,*p);
return 0;
}
|
样例输出
1
2
3
4
5
6
7
| a=1 //a的值
p=000000000062FE44 //p的值,即a的地址
*p=1 //p所指向的地址所存储的值,即a的值
a=22
p=000000000062FE44 //同上
*p=22
|
- 程序是用了来处理数据的,而数据实质就是存储在磁盘上的文件,因而文件操作是程序员必须学习的基础
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| FILE *f=NULL; //声明一个文件指针
char buff[100]; //声明一个字符串数组
char ch; //声明一个字符变量
int x; //声明一个整形变量
f=fopen("test.txt","wr"); //打开文件,"wr"指定可读可写
ch=fgetc(f); //从文件中读取单个字符赋值给ch
fscanf(f,"%s",buff); //从文件中读取字符串赋值给buff字符串数组
fscanf(f,"%d",&x); //从文件中读取一个整形变量
fgets(buff,100,f); //从文件中读取字符串,并存放到buff中,指定最大长度100
fprintf(f,"%s\n",buff); //向文件中写入字符串数组
fputc(ch,f); //向文件中写入单个字符
fputs(buff,f); //向文件中写入字符串
|
指针我这里暂时咕了,指针可以说是c语言的精髓,也意味着贯穿了整个c语言的所有内容,如果完成指针部分可能又花当前篇幅的一半。所以这里只有一个最最最基础的指针使用。如果你还需要详细的指针学习,推荐你前往这里,或许之后我也可能会把指针单独独立的讲解一次。
写着整个教程花了我接近三天的时间,而且越是到后面意志越是消沉(所以我写到指针咕咕咕了)
而且我知道这样实在是一个写的不怎么样的教程。
本教程尽量挑选了基础性的,使用较为常见的知识点进行讲解,并以本人自认为合适的一种顺序组织安排了全部内容
如果这篇博客能帮到你,非常高兴。
如果觉得博客有问题,欢迎邮件交流。