0 前言
1 第一个C程序
#include <stdio.h>
int max(int num1, int num2); // 函数声明
int main ()
{
/* 把两个数字保存到计算机中 */
int a = 100;
int b = 200;
int ret; // ret存放结果
/* 调用函数来获取最大值 */
ret = max(a, b);
printf( "Max value is : %d\n", ret );
return 0;
}
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
1.1 C语言规则
在C语言中不遵守编译器的规定,编译器在编译时就会报错,这个规定叫作规则。
- 预处理
#include <stdio.h>
#include
是一个预处理命令,用来引入头文件。printf()
函数在 stdio.h
头文件中声明。stdio.h
是一个头文件 (standard I/O) 。当编译器遇到 printf()
函数时,如果没有找到 stdio.h
头文件,会发生编译错误。
- 注释
// 函数声明
/* 把两个数字保存到计算机中 */
//
用于单行注释,/* ... */
用于多行注释。
- 声明
int max(int num1, int num2);
除主函数外,其他函数需要先声明后调用。
- 分号
在 C 程序中,分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。
- 主程序
int main ()
{
// code
return 0;
}
所有的 C 语言程序都需要包含 main()
函数。 代码从main()
函数开始执行。
现在 C 标准里规定 main()
返回值必须为int
,所以必须写成是int main()
。return
语句用于表示退出程序。main()
的返回值是 int
类型,所以是return 0
。
1.2 C语言规范
规范是人为的、约定成俗的,即使不按照那种规定也不会出错。
当企业或者项目有明确规范时,以企业或项目规范为准。
- 空行
两个相对独立的程序块、变量说明之后必须要加空行。空行起着分隔程序段落的作用。空行得体将使程序的布局更加清晰。空行不会浪费内存。
定义变量后要空行。尽可能在定义变量的同时初始化该变量,即遵循就近原则。
每个函数定义结束之后都要加空行。
- 空格
关键字之后要留空格。像
const
、case
等关键字之后至少要留一个空格,否则无法辨析关键字。像if
、for
、while
等关键字之后应留一个空格再跟左括号(
,以突出关键字。
函数名之后不要留空格,应紧跟左括号
(
,以与关键字区别。
(
向后紧跟;)
、,
、;
这三个向前紧跟;紧跟处不留空格。
,
之后要留空格。如果;
不是一行的结束符号,其后要留空格。
双目运算符的前后应当加空格。如
=
、==
、!=
、+=
、-=
、*=
、/=
、%=
、>>=
、<<=
、&=
、^=
、|=
、>
、<=
、>
、>=
、+
、-
、*
、/
、%
、&
、|
、&&
、||
、<<
、>>
、^
。
单目运算符
!
、~
、++
、--
、-(负号)
、*(指针)
、&(地址)
等前后不加空格。
像数组符号
[]
、结构体成员运算符.
、指向结构体成员运算符->
,这类操作符前后不加空格。
对于表达式比较长的
for
语句和if
语句,为了紧凑起见,双目运算符可以适当地去掉一些空格。但for
和if
后面紧跟的空格不可以删,其后面的语句可以根据语句的长度适当地去掉一些空格。例如for (i=0; i<10; i++)
- 对齐
{
和}
分别都要独占一行。互为一对的{
和}
要位于同一列,并且与引用它们的语句左对齐。
{}
之内的代码要向内缩进一个Tab
,且同一地位的要左对齐,地位不同的继续缩进。
- 代码行
一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且便于写注释。
if
、else
、for
、while
、do
等语句自占一行,执行语句不得紧跟其后。此外,非常重要的一点是,不论执行语句有多少行,就算只有一行也要加{},并且遵循对齐的原则,这样可以防止书写失误。
- 注释
当代码比较长,特别是有多重嵌套的时候,应当在段落的结束处加注释,这样便于阅读。
每一条宏定义的右边必须要有注释,说明其作用。
2 基本数据类型
2.1 位、字节与存储单元
计算机内存中的小存储单位为位(bit),每个位存储一个0
或1
。
计算机对内存管理的最小单位为字节(Byte),每字节为8位,计算机给每个字节分配一个十六进制表示的地址。
/***** 示例程序 *****/
#include<stdio.h>
int main()
{
int a = 10;
printf("a的地址为:%p", &a);
return 0;
}
/***** 示例结果 *****/
a的地址为:0x7ffc036a3fbc
计算机中一个数据存放占用的空间叫做存储单元,存储单元都是字节的整数倍。不同的数据会占用多个相邻的字节。编程中使用sizeof
来计算数据类型的存储单元大小。
/***** 示例程序 *****/
#include<stdio.h>
int main()
{
int a;
printf("int型数据的字节数为:%d", sizeof(a));
return 0;
}
/***** 示例结果 *****/
int型数据的字节数为:4
各类型数据的存储单元如下:
类型 | 32位平台 | 64位平台 |
---|---|---|
(unsigned /signed ) char | 1字节 | 1字节 |
(unsigned /signed ) short | 2字节 | 2字节 |
(unsigned /signed ) int | 4字节 | 4字节 |
(unsigned /signed ) long | 4字节 | 8字节 |
(unsigned /signed ) float | 4字节 | 4字节 |
(unsigned /signed ) double | 8字节 | 8字节 |
(unsigned /signed ) long long | 8字节 | 8字节 |
void | 4字节 | 8字节 |
指针 | 4字节 | 8字节 |
2.2 整数类型 int short long
可以先声明,也可以声明的同时赋值。
C语言支持一行声明或赋值多个变量,但不建议使用。
int earn;
short hogs = 21;
2.2.1 关键字 typedef
C 语言提供了typedef
关键字,您可以使用它来为数据类型取一个新的名字。
typedef
关键字常用来重新定义整数常量名称,使得整数类型的内存大小更直观。
typedef unsigned char uint8;
typedef short int int16;
typedef int int32;
#if __WORDSIZE == 64
typedef long int int64;
#else
__extension__
typedef long long int int64;
#endif
2.2.2 整数值范围
整数类型 | 范围 |
---|---|
uint8 | 0 到 255 |
int8 | -128 到 127 |
uint16 | 0 到 65,535 |
int16 | -32,768 到 32,767 |
uint32 | 0 到 4,294,967,295 |
int32 | -2,147,483,648 到 2,147,483,647 |
2.2.3 整数值格式
-
整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:
0x
或0X
表示十六进制,0
表示八进制,不带前缀则默认表示十进制。 -
整数常量也可以带一个后缀,后缀是
U
和L
的组合,U
表示无符号整数(unsigned
),L
表示长整数(long
)。后缀可以是大写,也可以是小写,U
和L
的顺序任意。
85 /* 十进制 */
0213 /* 八进制 */
0x4b /* 十六进制 */
30 /* 整数 */
30u /* 无符号整数 */
30l /* 长整数 */
30ul /* 无符号长整数 */
2.2.4 整数溢出
/***** 示例程序 *****/
#include<stdio>
int main()
{
int i =2147483647
unsigned int j = 4294967295;
printf("%d %d %d\n", i, i+1, i+2);
printf("%u %u %u\n", j, j+1, j+2);
return 0;
}
/***** 示例结果 *****/
2147483647 -2147483648 -2147483647
4294967295 0 1
整数变量为变量类型最大值+1
时,变量值变为变量类型最小值
,其他值依次类推。系统并不通知用户。最大值+1变为最小值,最小值-1变为最大值。
2.3 字符类型 char
char
类型存储字符,从技术上来看,char
类型实际上存储的是整数而不是字符,所以char
可以被typedef
为uint8
。
可以先声明,也可以声明的同时赋值。注意区分字符用‘ ’
,字符串用“ ”
。单字符的字符串比字符多一位'\0'
char grade; //正确
char grade = 'A'; //正确
char grade = 65; //正确
char grade = A; //错误,A是变量
char grade = "A"; //错误,“A”是字符串
2.3.1 转义字符
字符常量是括在单引号中,例如,'x'
可以存储在char
类型的简单变量中。
字符常量可以是一个普通的字符(例如'x'
)、一个转义序列,(例如 '\t'
),或一个通用的字符(例如 '\u02C0'
)。
在 C 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符\n
或制表符\t
等。
\\ \ 字符
\' ' 字符
\" " 字符
\? ? 字符
\a 警报铃声
\b 退格键
\f 换页符
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
2.4 浮点类型 float double
浮点常量由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。
当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用e
或 E
引入的。
3.14159 /* 合法的 */
314159E-5L /* 合法的 */
.2 /* 合法的 */
100. /* 合法的 */
3 复合数据类型
3.1 数组
数组用[ ]
选择下标,用{ }
输入元素。
#include <stdio.h>
int main ()
{
int n[10]; /* n 是一个包含 10 个整数的数组 */
int i,j;
/* 初始化数组元素 */
for ( i = 0; i < 10; i++ )
{
n[i] = i + 100; /* 设置元素 i 为 i + 100 */
}
/* 输出数组中每个元素的值 */
for (j = 0; j < 10; j++ )
{
printf("Element[%d] = %d\n", j, n[j] );
}
return 0;
}
数组可以是任意基本数据类型
int nannies[22];
char actors[26];
long big[500];
注意,char
类型的数组最后一位为\0
才成为字符串。
声明的同时定义数组。可以省略数组长度。
int arr[] = {5, 4, 3, 8};
3.1.1 多维数组
float rain[2][5] =
{
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10}
};
该数组理解为数组rain
有2个元素,每个元素为5位数组。数组的存储顺序为:
rain[0][0]
, rain[0][1]
, rain[0][2]
, rain[0][3]
, rain[0][4]
, rain[1][0]
, rain[1][1]
, rain[1][2]
, rain[1][3]
, rain[1][4]
3.2 字符串
在 C 语言中,字符串实际上是使用'\0'
终止的一维字符数组。因此,一个以'\0'
结尾的字符串,包含了组成字符串的字符。
‘\0’
字符与NULL
字符
‘\0’
是ASCII码定义的0号字符,即空字符,用于表示字符串结束。
NULL
是宏#define NULL (void*) 0
定义的指向不存在地址的指针,用于空指针。
下面的声明和初始化创建了一个 “Hello”
字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词“Hello”
的字符数多一个。
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
依据数组初始化规则,您可以把上面的语句写成以下语句:
char greeting[] = "Hello";
您可以使用空格做分隔符,把一个很长的字符串常量进行分行。
下面的实例显示了一些字符串常量。下面这三种形式所显示的字符串是相同的。
"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"
3.2.1 操作字符串的函数
# include <string>
函数 | 说明 |
---|---|
strcpy(s1, s2); | 复制字符串 s2 到字符串 s1。 |
strcat(s1, s2); | 连接字符串 s2 到字符串 s1 的末尾。 |
strlen(s1); | 返回字符串 s1 的长度。 |
strcmp(s1, s2); | 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。 |
strchr(s1, ch); | 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
strstr(s1, s2); | 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
3.3 枚举 enum
-
枚举允许一次性
#define
多个int
型常量 -
枚举默认第一个值是
0
,后面一个值是前一个值+1
-
可以将其中一个枚举量=整数值,后面的值依次
+1
/***** 示例程序 *****/
#include <stdio.h>
enum day_type
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
int main()
{
enum day_type day;
day = WED;
printf("%d",day);
return 0;
}
/***** 示例结果 *****/
3
3.4 结构体 struct
- 结构体作为函数参数
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 函数声明 */
void printBook( struct Books book );
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 输出 Book1 信息 */
printBook( Book1 );
/* 输出 Book2 信息 */
printBook( Book2 );
return 0;
}
void printBook( struct Books book )
{
printf( "Book title : %s\n", book.title);
printf( "Book author : %s\n", book.author);
printf( "Book subject : %s\n", book.subject);
printf( "Book book_id : %d\n", book.book_id);
}
- 指向结构体的指针
可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 &
运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
为了使用指向该结构的指针访问结构的成员,您必须使用->
运算符,如下所示:
struct_pointer->title;
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 函数声明 */
void printBook( struct Books *book );
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 通过传 Book1 的地址来输出 Book1 信息 */
printBook( &Book1 );
/* 通过传 Book2 的地址来输出 Book2 信息 */
printBook( &Book2 );
return 0;
}
void printBook( struct Books *book )
{
printf( "Book title : %s\n", book->title);
printf( "Book author : %s\n", book->author);
printf( "Book subject : %s\n", book->subject);
printf( "Book book_id : %d\n", book->book_id);
}
3.5 位域 struct
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0
和 1
两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。
struct k{
int a:1;
int :2; /* 该 2 位不能使用 */
int b:3;
int c:2;
};
-
由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位
-
位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的
main()
{
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */
pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */
pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */
pbit->b&=3; /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,
与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
pbit->c|=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */
}
4 运算符
4.1 算数运算符
算数运算符 | 说明 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取模运算符,整除后的余数 |
++ | 自增 |
-- | 自减 |
a++
和++a
的区别
#include <stdio.h>
int main()
{
int c;
int a = 10;
c = a++; //先赋值后运算,a=11,c=10
a = 10;
c = a--; //先赋值后运算,a=9,c=10
a = 10;
c = ++a; //先运算后赋值,a=11,c=11
a = 10;
c = --a; //先运算后赋值,a=9,c=9
}
- 除法运算符
/
注意数据类型,整数相除得到整数,结果的小数部分被抛弃。
#include <stdio.h>
int main()
{
int a;
float b;
a = 5/4; // a = 1
b = 5.0/4; // b = 1.250000
return 0;
}
4.2 关系运算符
关系运算符 | 说明 |
---|---|
== | 等于 |
!= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于或等于 |
<= | 小于或等于 |
4.3 逻辑运算符
逻辑运算符 | 说明 |
---|---|
&& | 逻辑与 |
|| | 逻辑或 |
! | 逻辑非 |
4.4 位运算符
位运算符 | 说明 |
---|---|
& | 位与 |
| | 位或 |
~ | 位取反 |
^ | 位异或 |
<< | 位左移 |
>> | 位右移 |
4.5 赋值运算符
赋值运算符 | 说明 |
---|---|
= | |
+= | |
-= | |
*= | |
/= | |
%= |
5 基本语法
5.1 关键字 break 和 continue
关键字 | 说明 |
---|---|
break | 退出所在的for , switch 循环体 |
continue | 结束当前循环迭代,进入下一步循环迭代 |
5.2 if 语句
if (表达式1)
语句1;
else if (表达式2)
语句2;
else
语句3;
5.3 switch 语句
switch(表达式)
{
case 常量表达式1:语句1;
break;
case 常量表达式2:语句2;
break;
...
default:语句n+1;
break;
}
注意switch
语句中一个case
满足后,程序依然会判断后面的case
是否满足,所以需要配合break
语句。
5.4 三元运算符
条件 ? 条件真时执行语句 : 条件假时执行语句
5.5 for 语句
#include<stdio.h>
#include<math.h>
int main()
{
int i,j;
printf("100以内的素数有:\n");
for(i=2; i<100; i++)
{
for(j=2; j<sqrt(i); j++)
{
if(i%j == 0)
{
break;
}
}
if(j > sqrt(i))
{
printf("%d,\t", i);
}
}
}
5.6 while 语句
#include <stdio.h>
int main()
{
int sum=0;
int num=1;
int sum2=0;
int num2=2;
while (num < 100)
{
sum=sum+num;
num=num+2;
}
printf("奇数和为:%d\n",sum);
while (num2 <= 100)
{
sum2=sum2+num2;
num2=num2+2;
}
printf("偶数和为:%d\n",sum2);
return 0;
}
6 指针
指针(pointer)是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
int* ip; /* 一个整型的指针 */
double* dp; /* 一个 double 型的指针 */
float* fp; /* 一个浮点型的指针 */
char* ch; /* 一个字符型的指针 */
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的int
型十六进制数。
但是指针声明时,必须声明成指针所指向的变量或常量的数据类型。指针如果指向一个float
型数据,则指针必须被声明为 float*
。这是因为不同数据类型的存储单元字节数不同,比如floae
型数据存储单元为4字节,意味着从float
的指针开始,连续读取4个字节才是完整的float
数据。
6.1 指针运算符
地址运算符 &
ptr = &val; // 把val的地址赋给ptr
所谓地址,是数据存储单元的第一个字节的字节地址。
间接运算符*
val = *ptr; // 找到ptr所存的地址,把内容赋值给val
在声明指针变量的时候,*ip
=指针地址,如果没有确切的地址可以赋值,为指针变量赋一个null
值是一个良好的编程习惯。
在使用指针变量的时候,*ip
= 实际变量值,ip
= 指针地址。
/***** 示例程序 *****/
#include <stdio.h>
int main ()
{
int* ptr = NULL;
printf("ptr 的地址是 %p\n", ptr );
return 0;
}
/***** 示例结果 *****/
ptr 的地址是 0x0
/***** 示例程序 *****/
#include <stdio.h>
int main ()
{
int var = 20; /* 实际变量的声明 */
int* ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
printf("var的地址: %p\n", &var );
/* 在指针变量中存储的地址 */
printf("ip存储的地址: %p\n", ip );
/* 使用指针访问值 */
printf("*ip 的值: %d\n", *ip );
return 0;
}
/***** 示例结果 *****/
var的地址: bffd8b3c
i存储的地址: bffd8b3c
*ip 的值: 20
6.2 指针与数组
数组名是数组首元素的地址,所以下面语句成立:
int arr[10];
arr == &arr[0];
指针+1
指的是指向下一个存储单元地址:
/***** 示例程序 *****/
#include<stdio.h>
int main()
{
int arr[10];
printf("%p %p", arr, arr+1);
return 0;
}
/***** 示例结果 *****/
0x7fffb2908ab0 , 0x7fffb2908ab4
arr
与arr+1
差值为4
,因为int
数据存储单元占4
个字节。arr+1
实际指的是arr
指针移动一个存储单元。
arr
是数组元素首地址,arr+index
是元素arr[index]
的地址,而*(arr+index)
是arr[index]
的值。
6.3 指针与函数
函数处理数组时,需要输入数组地址和数组元素个数。
#include<stdio>
#define SIZE 10
int sum(int arr[], int n);
int main()
{
int numble[SIZE] = {20, 10, 5, 39, 4, 16, 19, 26, 31, 20};
long answer;
answer = sum(numble, SIZE);
return 0;
}
int sum(int arr[], int n)
{
int i;
int total;
for (i=0; i<n; i++)
{
total += arr[i];
}
return total;
}
以下两种函数定义方式等价,都是传入指针:
int sum(int arr[], int n);
int sum(int* arr, int n);