// 预处理命令 #include 引入头文件 stdio.h
#include
// c语言程序代码从主函数 main() 开始执行
int main()
{
// printf() 函数已在 stdio.h 头文件中声明
printf("Hello, World! \n");
// 返回值 0 终止 main() 函数,退出程序
return 0;
}
关联知识点:30、命令行参数
hello.c
gcc hello.c
a.out
可执行文件,键入以下命令并回车:./a.out
请确保您的路径中已包含gcc编译器,并确保在包含源文件hello.c的目录中运行它
test1.c
、test2.c
,编译方法如下:gcc test1.c test2.c -o main.out
./main.out
;
结束。//单行注释
/*
多行
注释
*/
A-Z
或a-z
或_
开始,以字母/下划线/数字结束,不允许出现标点符号,大小写敏感。关键字 | 说明 |
---|---|
auto | 声明自动变量 |
break | 跳出当前循环 |
case | 开关语句分支 |
char | 声明字符型变量或函数返回值类型 |
const | 定义常量(变量被 const 修饰后,它的值不能再被改变) |
continue | 结束当前循环,开始下一轮循环 |
default | 开关语句中的"其它"分支 |
do | 循环语句的循环体 |
double | 声明双精度浮点型变量或函数返回值类型 |
else | 条件语句否定分支(与 if 连用) |
enum | 声明枚举类型 |
extern | 声明变量或函数是在其它文件或本文件的其他位置定义 |
float | 声明浮点型变量或函数返回值类型 |
for | 一种循环语句 |
goto | 无条件跳转语句 |
if | 条件语句 |
int | 声明整型变量或函数 |
long | 声明长整型变量或函数返回值类型 |
register | 声明寄存器变量 |
return | 子程序返回语句(可以带参数,也可不带参数) |
short | 声明短整型变量或函数 |
signed | 声明有符号类型变量或函数 |
sizeof | 计算数据类型或变量长度(即所占字节数) |
static | 声明静态变量 |
struct | 声明结构体类型 |
switch | 用于开关语句 |
typedef | 用以给数据类型取别名 |
unsigned | 声明无符号类型变量或函数 |
union | 声明共用体类型 |
void | 声明函数无返回值或无参数,声明无类型指针 |
volatile | 说明变量在程序执行中可被隐含地改变 |
while | 循环语句的循环条件 |
C99 新增关键字: | |
_Bool | ? |
_Complex | ? |
_Imaginary | ? |
inline | ? |
restrict | ? |
C11 新增关键字: | |
_Alignas | ? |
_Alignof | ? |
_Atomic | ? |
_Generic | ? |
_Noreturn | ? |
_Static_assert | ? |
_Thread_local | ? |
关联知识点:21、输入 & 输出
整数类型 | 关键字 | 存储大小 | 值范围 |
---|---|---|---|
字符型 | char | 1 字节 | 128 ~ 127 0 ~ 255 |
短整型 | short | 2 字节 | -32,768 ~ 32,767 |
整型 | int | 2 字节 4 字节 |
-32,768 ~ 32,767 -2,147,483,648 ~ 2,147,483,647 |
长整型 | long | 4 字节 | -2,147,483,648 ~ 2,147,483,647 |
长长整型 | long long | ? | ? |
整型修饰 | 关键字 | 说明 | 储值范围 |
---|---|---|---|
无符号 | unsigned | char默认无符号 加在类型前声明无符号 |
有符号类型的2倍 |
有符号 | signed | float、double总是有符号 默认声明的整型变量有符号 |
浮点类型 | 关键字 | 存储大小 | 值范围 | 精度 |
---|---|---|---|---|
单精度浮点型 | float | 4 字节 | 1.2E-38 ~ 3.4E+38 | 6 位 |
双精度浮点型 | double | 8 字节 | 2.3E-308 ~ 1.7E+308 | 15 位 |
长精度浮点型 | long double | 16 字节 | 3.4E-4932 ~ 1.1E+4932 | 19 位 |
#include
#include
#include
//头文件定义了宏,在程序中可以使用这些值和其他有关实数二进制表示的细节
int main()
{
printf("int 存储大小 : %lu \n", sizeof(int));
printf("float 存储最大字节数 : %lu \n", sizeof(float));
printf("float 最小值: %E\n", FLT_MIN );
printf("float 最大值: %E\n", FLT_MAX );
printf("精度值: %d\n", FLT_DIG );
return 0;
}
表示没有值的数据类型,通常用于:
void exit (int status);
int rand(void);
void *malloc( size_t size );
。关联知识点:25.1 显式转换
int i = 10;
float f = 3.14;
double d = i + f; // 隐式将int类型转换为double类型
double d = 3.14159;
int i = (int)d; // 显式将double类型转换为int类型
类型 | 关键字 | 占用内存 | 值范围 |
---|---|---|---|
布尔型 | bool | 1 个字节 | 0 或 1 |
字符型 | char | 1 个字节 | -128 到 127 或者 0 到 255 |
unsigned char | 1 个字节 | 0 到 255 | |
signed char | 1 个字节 | -128 到 127 | |
整型 | int | 4 个字节 | -2147483648 到 2147483647 |
unsigned int | 4 个字节 | 0 到 4294967295 | |
signed int | 4 个字节 | -2147483648 到 2147483647 | |
short int | 2 个字节 | -32768 到 32767 | |
unsigned short int | 2 个字节 | 0 到 65,535 | |
signed short int | 2 个字节 | -32768 到 32767 | |
long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | |
signed long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | |
unsigned long int | 8 个字节 | 0 到 18,446,744,073,709,551,615 | |
浮点型 | float | 4 个字节 | 精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字) |
双浮点型 | double | 8 个字节 | 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字) |
long double | 16 个字节 | 长双精度型 16 个字节(128位)内存空间,可提供18-19位有效数字。 | |
宽字符型 | wchar_t | 2 或 4 个字节 | 1 个宽字符 |
无类型 | void |
#include
#include
using namespace std;
int main()
{
cout << "type: \t\t" << "************size**************"<< endl;
cout << "bool: \t\t" << "所占字节数:" << sizeof(bool);
cout << "\t最大值:" << (numeric_limits<bool>::max)();
cout << "\t\t最小值:" << (numeric_limits<bool>::min)() << endl;
cout << "char: \t\t" << "所占字节数:" << sizeof(char);
cout << "\t最大值:" << (numeric_limits<char>::max)();
cout << "\t\t最小值:" << (numeric_limits<char>::min)() << endl;
cout << "signed char: \t" << "所占字节数:" << sizeof(signed char);
cout << "\t最大值:" << (numeric_limits<signed char>::max)();
cout << "\t\t最小值:" << (numeric_limits<signed char>::min)() << endl;
cout << "unsigned char: \t" << "所占字节数:" << sizeof(unsigned char);
cout << "\t最大值:" << (numeric_limits<unsigned char>::max)();
cout << "\t\t最小值:" << (numeric_limits<unsigned char>::min)() << endl;
cout << "wchar_t: \t" << "所占字节数:" << sizeof(wchar_t);
cout << "\t最大值:" << (numeric_limits<wchar_t>::max)();
cout << "\t\t最小值:" << (numeric_limits<wchar_t>::min)() << endl;
cout << "short: \t\t" << "所占字节数:" << sizeof(short);
cout << "\t最大值:" << (numeric_limits<short>::max)();
cout << "\t\t最小值:" << (numeric_limits<short>::min)() << endl;
cout << "int: \t\t" << "所占字节数:" << sizeof(int);
cout << "\t最大值:" << (numeric_limits<int>::max)();
cout << "\t最小值:" << (numeric_limits<int>::min)() << endl;
cout << "unsigned: \t" << "所占字节数:" << sizeof(unsigned);
cout << "\t最大值:" << (numeric_limits<unsigned>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned>::min)() << endl;
cout << "long: \t\t" << "所占字节数:" << sizeof(long);
cout << "\t最大值:" << (numeric_limits<long>::max)();
cout << "\t最小值:" << (numeric_limits<long>::min)() << endl;
cout << "unsigned long: \t" << "所占字节数:" << sizeof(unsigned long);
cout << "\t最大值:" << (numeric_limits<unsigned long>::max)();
cout << "\t最小值:" << (numeric_limits<unsigned long>::min)() << endl;
cout << "double: \t" << "所占字节数:" << sizeof(double);
cout << "\t最大值:" << (numeric_limits<double>::max)();
cout << "\t最小值:" << (numeric_limits<double>::min)() << endl;
cout << "long double: \t" << "所占字节数:" << sizeof(long double);
cout << "\t最大值:" << (numeric_limits<long double>::max)();
cout << "\t最小值:" << (numeric_limits<long double>::min)() << endl;
cout << "float: \t\t" << "所占字节数:" << sizeof(float);
cout << "\t最大值:" << (numeric_limits<float>::max)();
cout << "\t最小值:" << (numeric_limits<float>::min)() << endl;
cout << "size_t: \t" << "所占字节数:" << sizeof(size_t);
cout << "\t最大值:" << (numeric_limits<size_t>::max)();
cout << "\t最小值:" << (numeric_limits<size_t>::min)() << endl;
cout << "string: \t" << "所占字节数:" << sizeof(string) << endl;
// << "\t最大值:" << (numeric_limits::max)() << "\t最小值:" << (numeric_limits::min)() << endl;
cout << "type: \t\t" << "************size**************"<< endl;
return 0;
}
typedef type newname; // 使用 typedef 为一个已有的类型取一个新的名字
typedef int feet;
feet distance;
C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合;如果一个变量只有几种可能的值,可以定义为枚举类型。
enum 枚举名 {
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数]
} 枚举变量;
enum color { red, green, blue } c; // 0, 1, 2
c = blue;
enum color { red, green=5, blue }; // 0, 5, 6
// 第一个元素值默认为0
// 若指定某个元素的值,则后续元素的值依次+1,之前的元素不受影响
类型转换是将一个数据类型的值转换为另一种数据类型的值。
强制转换,通常用于比较类型相似的对象之间的转换。
静态转换不进行任何运行时类型检查,因此可能会导致运行时错误
int i = 10;
float f = static_cast<float>(i); // 静态将int类型转换为float类型
将一个基类指针或引用转换为派生类指针或引用。
动态转换在运行时进行类型检查,如果不能进行转换则返回空指针或引发异常
class Base {};
class Derived : public Base {};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base); // 将基类指针转换为派生类指针
将 const 类型的对象转换为非 const 类型的对象,且只能用于 const 属性,不能改变对象的类型。
const int i = 10;
int& r = const_cast<int&>(i); // 常量转换,将const int转换为int
将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换。
重新解释转换不进行任何类型检查,因此可能会导致未定义的行为
int i = 10;
float f = reinterpret_cast<float&>(i); // 重新解释将int类型转换为float类型
变量的名称必须以A-Z
或a-z
或_
开头,大小写敏感。
基本变量类型 | 描述 |
---|---|
char | 通常是一个字节(八位), 这是一个整数类型 |
int | 整型,4 个字节,取值范围 -2147483648 到 2147483647 |
float | 单精度浮点值,格式:1位符号,8位指数,23位小数 |
double | 双精度浮点值,格式:1位符号,11位指数,52位小数 |
void | 表示类型的缺失 |
//定义变量
int i, j, k;
char c, ch;
float f, salary;
double d;
//定义变量并初始化值
int d = 3, f = 5;
byte z = 22;
char x = 'x';
//带有静态存储持续时间的变量会被隐式初始化为 NULL
int i; //声明,也是定义
extern
关键字声明变量名而不定义它,其中变量可以在别的文件中定义extern int i; //声明,不是定义
除非有extern关键字,否则都是变量的定义
//变量在头部就已经被声明,但是定义与初始化在主函数内
#include
// 函数外定义变量 x 和 y
int x;
int y;
int addtwonum()
{
// 函数内声明变量 x 和 y 为外部变量
extern int x;
extern int y;
// 给外部变量(全局变量)x 和 y 赋值
x = 1;
y = 2;
return x+y;
}
int main()
{
int result;
// 调用函数 addtwonum
result = addtwonum();
printf("result 为: %d",result);
return 0;
}
//如果要在一个源文件中引用另外一个源文件中定义的变量,只需在引用的文件中将变量加上 extern 关键字的声明即可
变量是左值,因此可以出现在赋值号的左边。
数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。
十进制、八进制或十六进制的常量。
前缀基数 | 表示 | 后缀(不可重复) | 实例 |
---|---|---|---|
0 | 八进制 | 八进制数字 U、L 大小写任意组合 |
0213 |
十进制 | 十进制数字 U、L 大小写任意组合 |
85 30ul |
|
0x 0X |
十六进制 | 十六进制数字 U、L 大小写任意组合 |
0xFeeL 0x4b |
// 整数常量可以带有一个后缀表示数据类型
int myInt = 10;
long myLong = 100000L;
unsigned int myUnsignedInt = 10U;
由整数部分、小数点、小数部分和指数部分组成。
格式 | 要求 | 实例 |
---|---|---|
小数形式 | 整数+小数 | 3.14159 |
指数形式 | 小数点+指数 带符号的指数用 e 或 E 引入 |
314159E-5L |
// 浮点数常量可以带有一个后缀表示数据类型
float myFloat = 3.14f;
double myDouble = 3.14159;
一个普通的字符、转义序列或通用的字符,括在单引号’…'中。
转义序列 | 含义 |
---|---|
\\ | \ 字符 |
\' | ' 字符 |
\" | " 字符 |
\? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
printf("Hello\tWorld\n\n"); // 输出 Hello World
// 字符常量的 ASCII 值可以通过强制类型转换转换为整数值
char myChar = 'a';
int myAsciiValue = (int) myChar; // 将 myChar 转换为 ASCII 值 97
包含字符和转义序列,括在双引号"…"中,可以分行。 关联知识点:16、字符串
// 字符串常量在内存中以 null 终止符 \0 结尾
char myString[] = "Hello, world!"; // 系统对字符串常量自动添加'\0'
// 定义一个不可更改的全局字符串常量
const char product_name[] = "The program version 3";
// 将字符串常量定义为局部变量(const char * 指针变量)
int main() {
const char *s1 = "world";
printf("Hello %s\n", s1);
return 0;
}
常量又叫做字面量,可以是任何的基本数据类型,常量的值在定义后不能进行修改,可以直接在代码中使用,也可以通过定义常量来使用。
【好习惯】把常量定义为大写字母形式!
#define
预处理指令关联知识点:23.1 预处理器
#include
/* #define 定义常量值,末尾没有分号 */
#define LENGTH 10
#define WIDTH 5
#define NEWLINE '\n'
int main()
{
int area;
area = LENGTH * WIDTH;
printf("value of area : %d", area);
printf("%c", NEWLINE);
return 0;
}
const
关键字#include
int main()
{
// const 定义常量(必须在一条语句内完成)
const int LENGTH = 10;
const int WIDTH = 5;
const char NEWLINE = '\n';
int area;
area = LENGTH * WIDTH;
printf("value of area : %d", area);
printf("%c", NEWLINE);
return 0;
}
定义变量/函数的存储位置、生命周期和作用域,存储类修饰符加在类型之前。
auto
存储类所有局部变量默认为 auto ,它们在函数开始时被创建,在函数结束时被销毁。
{
int mount;
auto int month; // auto 只能修饰函数内的局部变量
}
register
存储类定义存储在寄存器中的局部变量,变量的最大尺寸等于寄存器的大小,在需要频繁访问的变量上使用 register 可以提高程序的运行速度,但是它不能直接取地址,也不能应用一元的 ‘&’ 运算符。
{
register int miles;
}
// register 变量不一定存储在寄存器中,取决于硬件和实现的限制
static
存储类修饰 | 说明 |
---|---|
局部变量 | 在程序的生命周期内保持局部变量,可跨函数调用 变量值只初始化一次,之后调用不会重置,而是累计运行 |
全局变量 | 限制作用域在声明它的文件内,可以被文件内任何函数调用 |
#include
/* 函数声明 */
void func1(void);
/* 全局变量 - static 是默认的 */
static int count=10;
int main()
{
while (count--) {
func1();
}
return 0;
}
void func1(void)
{
// thingy只初始化一次,之后调用函数不会重置,而是累加++
static int thingy=5;
thingy++;
}
extern
存储类定义在其他文件中声明的全局变量或函数,不会为变量分配任何存储空间,仅提供一个全局变量的引用,通常用于两个或多个文件共享相同的全局变量或函数。
// 第一个文件 main.c
#include
int count ; // 定义全局变量count
extern void write_extern(); // 引用全局函数write_extern()
int main()
{
count = 5;
write_extern();
}
// 第二个文件 support.c
#include
extern int count; // 引用全局变量count
void write_extern(void) // 定义全局函数write_extern()
{
printf("count is %d\n", count);
}
运算符 | 描述 | 设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 |
int c, a;
a = 10;
c = a++; // 先赋值c=a=10,后运算a=a++=11
a = 10;
c = a--; // 先赋值c=a=10,后运算a=a--=9
a = 10;
c = ++a; // 先运算a=a++=11,后赋值c=a=11
a = 10;
c = --a; // 先运算a=a--=9,后赋值c=a=9
运算符 | 描述 | 设A=10、B=20 |
---|---|---|
== | 两个操作数的值相等则条件为真 | (A == B) 为假 |
!= | 两个操作数的值不相等则条件为真 | (A != B) 为真 |
> | 左操作数的值大于右操作数的值则条件为真 | (A > B) 为假 |
< | 左操作数的值小于右操作数的值则条件为真 | (A < B) 为真 |
>= | 左操作数的值≥右操作数的值则条件为真 | (A >= B) 为假 |
<= | 左操作数的值≤右操作数的值则条件为真 | (A <= B) 为真 |
运算符 | 描述 | 设A=1、B=0 |
---|---|---|
&& | 逻辑与运算符(两个操作数都非零则条件为真) | (A && B) 为假 |
|| | 逻辑或运算符(两个操作数任意非零则条件为真) | (A || B) 为真 |
! | 逻辑非运算符(逆转操作数的真假逻辑状态) | !(A && B) 为真 |
运算符 | 描述 | 设A=60、B=13,得 |
---|---|---|
& | 二进制位"与"运算(仅当1&1=1) 0&0=0; 0&1=0; 1&0=0; 1&1=1; |
A & B == 12 60 → 00111100 13 → 00001101 ———————— 12 ← 00001100 |
| | 二进制位"或"运算(仅当0|0=0) 0|0=0; 0|1=1; 1|0=1; 1|1=1; |
A | B == 61 60 → 00111100 13 → 00001101 ———————— 61 ← 00111101 |
^ | 二进制位"异或"运算(相同为0,相异为1) 0^0=0; 0^1=1; 1^0=1; 1^1=0; |
A ^ B == 49 60 → 00111100 13 → 00001101 ———————— 49 ← 00110001 |
~ | 二进制位"取反"运算(二进制数的补码形式) ~A=-(A+1) |
~A == -61 |
<< | 二进制“左移”运算(按位左移,右边补0) | A << 2 == 240 |
>> | 二进制“右移”运算(按位右移,正数左补0、负数左补1) | A >> 2 == 15 |
※进制转换的方法:
- 十进制→二进制:短除法,将所有0、1从上到下连起来从右至左摆放,不够八位数的左边补0
2 | 60 ‾ \underline{\text{60}} 60 2 | 13 ‾ \underline{\text{13}} 13
2 | 30 ‾ \underline{\text{30}} 30 ······ 0 2 | 6 ‾ \underline{\text{6}} 6 ······ 1
2 | 15 ‾ \underline{\text{15}} 15 ······ 0 2 | 3 ‾ \underline{\text{3}} 3 ······ 0
2 | 7 ‾ \underline{\text{7}} 7 ······ 1 1 ······ 1
2 | 3 ‾ \underline{\text{3}} 3 ······ 1
1 ······ 1
60 → 00111100 13 → 00001101
- 二进制→十进制:从右至左每个数字依次乘以 2 n − 1 2^{n-1} 2n−1,将乘积相加
0 0 0 0 1 1 0 1
x 2 7 2^7 27 x 2 6 2^6 26 x 2 5 2^5 25 x 2 4 2^4 24 x 2 3 2^3 23 x 2 2 2^2 22 x 2 1 2^1 21 x 2 0 2^0 20
—————————————————
0 + 0 + 0 + 0 + 8 + 4 + 0 + 1 = 13
- 二进制取反:使用补码来表示二进制数,最高位为符号位,0正、1负
① 正数取反(补码=原码)
60 → 00111100 ————→ 11000011 ———→ 10111101 → -61
正数 原码/补码 按位取反 负数 取补码 负数原码
② 负数取反(补码=原码符号位不变+剩余位取反+1)
-60 → 10111100 → 11000100 ————→ 00111011 → 59
负数 原码 补码 按位取反 正数补码/原码
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符 | C = A + B |
+= | 加且赋值运算符 | C += A → C = C + A |
-= | 减且赋值运算符 | C -= A → C = C - A |
*= | 乘且赋值运算符 | C *= A → C = C * A |
/= | 除且赋值运算符 | C /= A → C = C / A |
%= | 求模且赋值运算符 | C %= A → C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 → C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 → C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 → C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 → C = C ^ 2 |
|= | 按位或且赋值运算符 | C |= 2 → C = C | 2 |
运算符 | 描述 | 实例 |
---|---|---|
sizeof() | 返回变量的字节大小 | sizeof(a) 将返回 4(a 是整数) |
& | 返回变量的地址 | &a; 将给出变量的实际地址 |
* | 指向一个变量 | *a; 将指向一个变量 |
? : | 条件表达式 | 如果条件为真 ? 则值为 X : 否则值为 Y |
优先级 | 类别 | 运算符 |
---|---|---|
1 | 后缀 | () [] -> . ++ – |
2 | 一元 | + - ! ~ ++ – (type)* & sizeof |
3 | 乘除 | * / % |
4 | 加减 | + - |
5 | 移位 | << >> |
6 | 关系 | < <= > >= |
7 | 相等 | == != |
8 | 位与 AND | & |
9 | 位异或 XOR | ^ |
10 | 位或 OR | | |
11 | 逻辑与 AND | && |
12 | 逻辑或 OR | || |
13 | 条件 | ? : |
14 | 赋值 | = += -= *= /= %= >>= <<= &= ^= |= |
15 | 逗号 | , |
0
/ null
假定为 false,其他非零和非空的值假定为 true。
语句 | 描述 |
---|---|
if()... |
一个 if 语句由一个布尔表达式后跟一个或多个语句组成 |
if()...else... |
if 语句后可跟一个 else 语句,在布尔表达式为假时执行 |
switch()... |
一个 switch 语句允许测试一个变量等于多个值时的情况 |
... ? ... : ... |
条件运算符 ? :,可以用来替代 if…else 语句 |
循环语句允许我们多次执行一个语句或语句组。
循环类型 | 描述 |
---|---|
while()... |
在执行循环主体之前测试条件,当条件为真时重复语句 |
for()... |
多次执行一个语句序列,简化管理循环变量的代码 |
do...while() |
在循环主体结尾测试条件,当条件为真时重复语句 |
控制语句 | 描述 |
---|---|
break; |
跳过循环或 switch 语句,程序流将继续执行下一条语句 |
continue; |
立刻停止本次循环迭代,重新开始下次循环迭代 |
goto ...; |
将控制转移到被标记的语句(不建议使用) |
#include
int main ()
{
for( ; ; ) // 可以将某些条件表达式留空来构成一个无限循环
{
printf("该循环会永远执行下去!\n");
}
return 0;
}
// 可以按 Ctrl + C 键终止一个无限循环
C 标准库提供了大量的程序可以调用的内置函数
函数声明告诉编译器函数名称及如何调用函数,函数的实际主体可以单独定义。
return_type function_name( params );
// return_type - 函数返回的值的数据类型(void不返回值)
// function_name - 函数的实际名称,和参数列表一起构成了函数签名
// params - 函数向参数传递的值称为实际参数,包括类型、顺序、数量
int max(int num1, int num2);
int max(int, int); // 参数的类型是必需的,参数的名称可不填
char * strcat(const char *s, int c); // 返回值为指针的函数
// 调用其他文件的函数时,必须在当前文件顶部声明函数
在 C 语言中,函数由一个函数头和一个函数主体组成。
return_type function_name( params )
{
// 函数主体包含一组定义函数执行任务的语句
...body...
}
/* 定义函数返回一个整型值 */
int max(int num1, int num2)
{
int result;
if (num1 > num2) {
result = num1;
} else {
result = num2;
}
return result;
}
函数声明接受参数值的变量称为形式参数,进入函数时创建,退出函数时销毁;调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值。
实参:可以是常量、变量、表达式、函数等任意类型
形参:只能是变量,且在被定义的函数中必须指定形参的类型
把参数的实际值复制给函数的形式参数,修改函数内的形式参数不会影响实际参数。
#include
/* 声明函数,声明参数类型为 int */
void swap(int x, int y);
int main ()
{
/* 定义变量并初始化 */
int a = 100;
int b = 200;
/* 传入变量(实参)的值a,b,复制给形参,函数内操作形参 */
swap(a, b);
return 0;
}
/* 定义函数(参数类型为 int) */
void swap(int x, int y)
{
int temp;
temp = x; // 保存 x 的值
x = y; // 把 y 赋值给 x
y = temp; // 把 temp 赋值给 y
return;
}
// 传值调用在函数内改变了 a 和 b 的值,但实际上 a 和 b 的值没有变化
形参为指向实参地址的指针,对形参的操作即对实参本身的操作,传递指针可以让多个函数访问指针所引用的对象,而不用全局声明。
关联知识点:14.5 传递指针给函数
#include
/* 声明函数,声明参数类型为指向 int 的指针 */
void swap(int *x, int *y);
int main ()
{
/* 定义变量并初始化 */
int a = 100;
int b = 200;
/* 传入变量(实参)的地址&a,&b,赋值给形参,函数内操作实参 */
swap(&a, &b);
return 0;
}
/* 定义函数(参数类型为指向 int 的指针) */
void swap(int *x, int *y)
{
int temp;
temp = *x; // 保存地址 x 的值
*x = *y; // 把 y 赋值给 x
*y = temp; // 把 temp 赋值给 y
return;
}
// 引用调用在函数内改变了 a 和 b 的值,也改变了实际上 a 和 b 的值
局部变量:在某个函数或块的内部声明的变量,只能被内部的语句使用。
全局变量:定义在函数外部(程序顶部),可以被程序内任何函数访问。
形式参数:相当于该函数内的局部变量。
#include
/* 全局变量声明 */
int a = 20;
int main ()
{
/* 在主函数中的局部变量声明 */
int a = 10;
int b = 20;
int c = 0;
int sum(int, int);
c = sum( a, b);
return 0;
}
/* 添加两个整数的函数 */
int sum(int a, int b)
{
return a + b;
}
- 全局变量保存在内存的全局存储区中,占用静态的存储单元
- 局部变量保存在栈中,只有在函数被调用时才动态地分配存储单元
定义局部变量时,必须手动对其初始化;定义全局变量时,系统会自动初始化。
【好习惯】正确地初始化变量,否则有时候程序可能会产生意想不到的结果,因为未初始化的变量会导致一些在内存位置中已经可用的垃圾值!
数据类型 | 初始化默认值 |
---|---|
int | 0 |
char | ‘\0’ |
float | 0 |
double | 0 |
pointer | NULL |
#include
int main ()
{
/* 局部变量声明 */
int a, b;
int c;
/* 实际初始化 */
a = 10;
b = 20;
c = a + b;
return 0;
}
数组用来存储连续内存位置的数据,往往被认为是一系列相同类型的变量。
type arrayName[arraySize]; // 一维数组
// type - 任意有效的 C 数据类型
// arraySize - 一个大于零的整数常量
double balance[10];
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
// {}中元素数量不能大于[]中指定的长度
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
// 若不填[]长度,则数组长度为初始化时{}中的元素数量
balance[4] = 50.0; // 为数组中某个元素赋值
double salary = balance[9]; // 通过索引对数组进行访问
#include
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;
}
一维数组的列表,一个带有 x 行和 y 列的表格。
type arrayName[x][y];
// type - 任意有效的 C 数据类型
// arrayName - 一个有效的 C 标识符
// 数组中的每个元素使用形式为 arr[ i , j ] 的元素名称来标识
int x[3][2];
// 初始化二维数组:为每行指定值
int a[3][3] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; // 内部嵌套的括号
// 访问二维数组元素:数组的行索引和列索引
int val = a[2][5];
在函数中传递一个数组作为参数,必须声明函数形式参数。
// 方式1:形式参数是一个指向数组的指针
void myFunction(int *param) { ... }
// 方式2:形式参数是一个已定义大小的数组
void myFunction(int param[10]) { ... }
// 方式3:形式参数是一个未定义大小的数组
void myFunction(int param[]) { ... }
#include
/* 函数声明 */
double getAverage(int arr[], int size);
int main ()
{
/* 带有 5 个元素的整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ;
return 0;
}
double getAverage(int arr[], int size)
{
int i;
double avg;
double sum=0;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = sum / size;
return avg;
}
C 语言不允许返回一个完整的数组作为函数的参数,但可以通过指定不带索引的数组名来返回一个指向数组的指针。
关联知识点:14.6 从函数返回指针
#include
#include
#include
// 如果要从函数返回一个数组,必须声明一个返回指针的函数
int *getRandom( )
{
// 如需在函数外返回局部变量的地址,只能 static 局部变量
static int r[10];
int i;
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i)
{
r[i] = rand();
}
return r;
}
int main ()
{
int *p;
int i;
p = getRandom();
for ( i = 0; i < 10; i++ )
{
printf( "*(p + %d) : %d\n", i, *(p + i)); // p[i]的值
}
return 0;
}
数组名是一个指向数组中第一个元素的常量指针。
// 声明数组后,数组名 balance 即是一个指向 balance[0] 的指针
double balance[10];
double *p;
p = balance; // 赋值数组内第一个元素的地址
// p = &balance[0];
*(balance + 4) // 访问 balance[4] 数据
*(p + 0) // 赋值后可通过 *p 访问数组元素
// 一旦我们有了 p 中的地址,*p 将给出存储在 p 中相应地址的值
一种基本数据类型,用于定义一组具有离散值的常量。
使用 enum
关键字定义枚举类型,每个枚举常量可以用一个标识符来表示,也可以指定一个整数值,如果没有指定则默认从 0 开始递增。
enum 枚举名 {枚举元素1, 枚举元素2, ……};
enum DAY { MON=1, TUE, WED, THU, FRI }; // 1,2,3,4,5
enum season { spring, summer=3, autumn, winter }; // 0,3,4,5
// 第一个元素值默认为0
// 若指定某个元素的值,则后续元素的值依次+1,之前的元素不受影响
// 方式1:先定义枚举类型DAY,再定义枚举变量day
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
// 方式2:定义枚举类型DAY的同时定义枚举变量day
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
// 方式3:省略枚举名称,直接定义枚举变量day
enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
枚举类型被当作 int 或 unsigned int 处理,C 语言无法遍历枚举类型,但连续的枚举类型可以实现有条件的遍历。
#include
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
enum color { red=1, green, blue };
enum color favorite_color;
int main()
{
// 仅当枚举的元素值全部连续时,可以for遍历
for (day = MON; day <= SUN; day++) {
printf("枚举元素:%d \n", day);
}
// 枚举可以进行switch匹配
switch (favorite_color)
{
case red:
printf("你喜欢的颜色是红色");
break;
case green:
printf("你喜欢的颜色是绿色");
break;
case blue:
printf("你喜欢的颜色是蓝色");
break;
default:
printf("你没有选择你喜欢的颜色");
}
}
#include
#include
int main()
{
enum day
{
saturday,
sunday,
monday,
tuesday,
wednesday,
thursday,
friday
} workday;
int a = 1;
enum day weekend;
weekend = ( enum day ) a; // 整数类型转换为枚举类型
return 0;
}
指针也就是内存地址,指针变量是用来存放内存地址的变量。
type *var_name;
// type - 指针的基类型,必须是一个有效的 C 数据类型
// * - 指定一个变量是指针
// var_name - 指针变量的名称
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
// 所有数据类型对应指针的值都是一个代表内存地址的长的十六进制数
// 不同数据类型的指针所指向的变量或常量的数据类型不同
每一个变量的内存位置都定义了可使用 &
运算符访问的地址,使用 *
运算符来返回位于操作数所指定地址的变量的值。
#include
int main ()
{
int var = 20; // 声明实际变量 var
int *ip; // * 声明指针变量 ip
ip = &var; // 将 var 的地址赋值给 ip
printf("ip 变量存储的地址: %p\n", ip ); // ip == var的地址
printf("*ip 变量的值: %d\n", *ip ); // *ip == var的值
return 0;
}
// 指针的数据类型必须与指向的变量的数据类型一致
赋值为 NULL 的指针,一个定义在标准库中的值为零的常量。
【好习惯】在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值!
int *ptr = NULL; // ptr 的地址是 0x0
// 内存地址 0 表明该指针不指向一个可访问的内存位置
C 指针是一个用数值表示的地址,可以进行 ++、–、+、- 运算。
#include
const int MAX = 3;
int main ()
{
int num = 1000;
int *n;
n = #
n++; // 指针每一次 ++ 指向下一个整数(内存位置后移 4 字节)
n--; // 指针每一次 -- 指向上一个整数(内存位置前移 4 字节)
// 使用指针代替数组,方便顺序访问数组中的每一个元素
int var[] = {10, 100, 200};
int i, *ptr;
ptr = var;
// ptr = &var[0];
for ( i = 0; i < MAX; i++)
{
ptr++; // 指针每一次 ++ 指向数组中下一个元素
ptr--; // 指针每一次 -- 指向数组中上一个元素
}
return 0;
}
// 指针每次增减的跨度,取决于指针所指向的数据类型长度
指针可以用关系运算符 ==、<、> 进行大小比较。
#include
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;
ptr = var;
// ptr = &var[0];
i = 0;
while ( ptr <= &var[MAX - 1] )
{
ptr++;
i++;
}
return 0;
}
int *ptr[n]; // ptr 中每个元素都是一个指向 int 值的指针
// ptr[] - 声明为一个数组
// n - 指定数组的长度
#include
const int MAX = 3;
int main ()
{
// 用指向整数的指针数组来存储一个整数列表
int var[] = {10, 100, 200};
int i, *ptr[MAX];
for ( i = 0; i < MAX; i++)
{
ptr[i] = &var[i]; // 给 ptr 中每个指针赋值 var 中元素的地址
}
// 用指向字符的指针数组来存储一个字符串列表
const char *names[] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
return 0;
}
一种多级间接寻址的形式,或者说是一个指针链,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
#include
int main ()
{
int V = 100;
int *Pt1;
int **Pt2; // ** 声明一个指向 Pt1 的指针
Pt1 = &V; // 给 Pt1 赋值 V 的地址
Pt2 = &Pt1; // 给 Pt2 赋值 Pt1 的地址
printf("Pt1 = %p\n", Pt1 ); // Pt1 == var的地址
printf("*Pt1 = %d\n", *Pt1 ); // *Pt1 == var的值
printf("Pt2 = %p\n", Pt2 ); // Pt2 == Pt1的地址
printf("**Pt2 = %d\n", **Pt2); // **Pt2 == Pt1的值 == var的值
return 0;
}
#include
#include
// 声明函数的参数为指针类型
void getSeconds(unsigned long *par); // 不返回值
double getAverage(int *arr, int size); // 返回 double 类型值
int main ()
{
unsigned long sec;
getSeconds( &sec ); // 传递实际变量 sec 到函数内部
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
avg = getAverage( balance, 5 ); // 传递指向数组的指针 balance
return 0;
}
void getSeconds(unsigned long *par)
{
*par = time( NULL ); // 在函数内改变这个实际变量
return;
}
double getAverage(int *arr, int size)
{
int i, sum = 0;
double avg;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = (double)sum / size;
return avg;
}
// 声明一个返回指针的函数
int * myFunction() { ... }
#include
#include
#include
int * getRandom( )
{
static int r[10]; // static 使函数能够返回局部变量的地址
int i;
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i)
{
r[i] = rand();
}
return r;
}
int main ()
{
int *p;
int i;
p = getRandom(); // 调用函数,将返回值赋值给指针变量
for ( i = 0; i < 10; i++ )
{
printf("*(p + [%d]) : %d\n", i, *(p + i) );
}
return 0;
}
※指针形式的含义总结:
int var = 10; // var -(整型)变量,值为10 // &var - var的存储地址 int *poi; poi = &var; // *poi -(指向整型)指针,值为var(10) // poi - 指针变量,值为&var(var的存储地址,地址信息固定大小4byte) // &poi - poi的存储地址 int *poi_new; poi_new = poi + 5; // = &var + (5 x sizeof(int)) // = var的存储地址 + 5个整型存储大小 // = 从var的地址向后移动20个字节所在的地址 // = 第6个int变量的地址(假设var在第1个) /* 以下以一个数组空间为例 */ int arr[] = {2, 0, 2, 3}; *poi = arr; // arr - 数组的首元素arr[0],值为2 poi_new = *poi + 1; // == poi[0] + 1 // = arr[0] + 1 // = 2 + 1 // = 3 poi_new = *(poi + 1); // == poi[1] // = *(&arr[0] + (1 x sizeof(int))) // = *(&arr[1]) // = arr[1] // = 0
引用变量是一个别名,是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
引用 | 指针 |
---|---|
不存在空引用,必须链接到一块合法的内存 | 可以是空指针 |
必须在创建时被初始化 | 可以在任何时间被初始化 |
一旦初始化,就不能再指向另一个对象 | 可以在任何时候指向另一个对象 |
把变量名称当作变量附属在内存位置中的标签,引用则是其第二个标签
// 通过原始变量名称或引用来访问变量的内容
int i = 17;
int& r = i; // r 是一个初始化为 i 的整型引用
#include
using namespace std;
int main ()
{
// 声明简单的变量
int i;
double d;
// 声明引用变量
int& r = i;
double& s = d;
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}
#include
using namespace std;
// 函数声明
void swap(int& x, int& y);
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
// 调用函数
swap(a, b); // 直接传入变量作为参数
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
// 函数定义
void swap(int& x, int& y) // 引用传入参数的地址
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
引用替代指针,当函数返回一个引用时,则返回一个指向返回值的隐式指针。
#include
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
// 函数返回值为一个引用
double& setValues(int i) {
double& ref = vals[i]; // 引用变量 ref 引用 vals[i]
return ref; // 返回第 i 个元素的引用
// static int x; 若要返回局部变量的引用,需声明 static 存储类
// return x;
}
// 要调用上面定义函数的主函数
int main ()
{
cout << "改变前的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
// 返回引用的函数可以直接放在赋值语句的左边,相当于一个指针
setValues(1) = 20.23; // 改变第 2 个元素
setValues(3) = 70.8; // 改变第 4 个元素
cout << "改变后的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
// 注意被引用的对象不能超出作用域
函数指针是指向函数的指针变量,可以像一般函数一样调用函数、传递参数。
// 声明一个指向同样参数、返回值的函数指针类型
typedef int (*fun_ptr)(int,int);
#include
/* 声明函数max() */
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
int (*p)(int, int) = &max; // 声明指向max()的函数指针,&可省略
// int (*p)(int, int) = max;
int a, b, c, d;
d = p(p(a, b), c); // 相当于直接调用函数
// d = max(max(a, b), c);
return 0;
}
函数指针可以作为某个函数的参数来使用,被参数调用的函数称为回调函数。
#include
#include
// 参数 int (*getNextValue)(void) 是函数指针,用来设置数组的值
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}
// 回调函数 getRandom() 返回值将作为函数指针传递给 populate_array()
int getRandom(void)
{
return rand();
}
int main(void)
{
int array[10];
populate_array(array, 10, getRandom); // 传入 getRandom 本身
for(int i = 0; i < 10; i++) {
printf("%d ", array[i]);
}
printf("\n");
return 0;
}
使用空字符 \0
结尾的一维字符数组,空字符(Null character, NUL)又称结束符,是一个数值为 0 的控制字符,转义为 \0 用于标记字符串的结束。
// 声明和初始化一个 RUNOOB 字符串
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
char site[] = "RUNOOB";
// C 编译器会在初始化数组时,自动把 \0 放在字符串的末尾
函数 | 返回 | 含义 | 描述 |
---|---|---|---|
strcpy(s1, s2); |
s1=s2 | Copy | 复制字符串 s2 到字符串 s1 |
strcat(s1, s2); |
s1+s2 | Catenate | 连接字符串 s2 到字符串 s1 的末尾 |
strlen(s1); |
int | Length | 返回字符串 s1 的长度 |
strcmp(s1, s2); |
0 负数 正数 |
Compare | 比较两个字符串并返回整数: 如果 s1 == s2 ,则返回 0 如果 s1<s2,则返回负数 如果 s1>s2,则返回正数 |
strchr(s1, ch); |
*char | Char | 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置 |
strstr(s1, s2); |
*string | String | 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置 |
允许用户自定义数据类型,存储不同类型的数据项,结构体中的数据成员可以是基本数据类型,也可以是其他结构体类型、指针类型等。
结构体定义由关键字 struct
和结构体名(自定义)组成。
// 定义一个包含多个成员的新的数据类型
struct tag {
definition A;
definition B;
...
} variables;
// tag - 结构体标签
// definition - 标准的变量定义,比如 int i;
// variables - 指定一个或多个结构变量,定义在结构的末尾
关联知识点:20、别名
// 方式1:不命名标签,直接声明变量
struct
{
int a;
char b;
double c;
} s1; // s1 与 t1, t2[20], *t3 是完全不同的数据类型,不可相互赋值
// 方式2:命名标签,但不声明变量,另外用标签声明新的结构体变量
struct SIMPLE
{
int a;
char b;
double c;
};
struct SIMPLE t1, t2[20], *t3; // 声明 SIMPLE 类型的变量
// 方式3:用 typedef 给结构体命名,作为类型声明新的结构体变量
typedef struct
{
int a;
char b;
double c;
} Simple2;
Simple2 u1, u2[20], *u3; // 用 Simple2 作为类型声明新的结构体变量
// 结构1:声明包含其他的结构体
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};
// 结构2:声明包含指向自己类型的指针
struct NODE
{
char string[100];
struct NODE *next_node;
};
// 如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明
struct B; // 对结构体B进行不完整声明
struct A
{
struct B *partner; //结构体A中包含指向结构体B的指针
...
};
struct B // 在A声明完后,B也随之进行声明
{
struct A *partner; // 结构体B中包含指向结构体A的指针
...
};
#include
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456}; // 变量初始化
int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}
使用成员访问运算符 .
访问结构的成员。
#include
#include
/* 定义数据类型 Books */
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
/* 使用类型 Books 声明变量 Book1 */
struct Books Book1;
/* 访问 Book1 结构成员并赋值 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* 访问 Book1 结构成员并输出 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);
return 0;
}
可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。
关联知识点:10.3 调用传参
#include
#include
/* 定义数据类型 Books */
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 声明函数,声明参数类型为 struct Books */
void printBook( struct Books book );
int main( )
{
/* 使用类型 Books 声明变量 Book2 */
struct Books Book2;
/* 访问 Book2 结构成员并赋值 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 传值调用 */
printBook( Book2 );
return 0;
}
/* 定义函数(参数类型为 struct Books) */
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);
}
可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似。
关联知识点:14、指针
struct Books *struct_pointer; // * 定义指向结构的指针
struct_pointer = &Book1; // & 在指针变量中存储结构变量的地址
struct_pointer->title; // -> 使用指针访问结构的成员
#include
#include
/* 定义数据类型 Books */
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 声明函数,声明参数类型为指向 struct Books 的指针 */
void printBook( struct Books *book );
int main( )
{
/* 使用类型 Books 声明变量 Book2 */
struct Books Book2;
/* 访问变量 Book2 结构成员并赋值 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 引用调用 */
printBook( &Book2 );
return 0;
}
/* 定义函数(参数类型为指向 struct Books 的指针) */
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);
}
在相同的内存位置存储不同的数据类型,可以定义一个带有多成员的共用体,但只能有一个成员带有值。
关联知识点:17、结构体
使用 union
语句定义共用体,方式与定义结构类似。
// 定义一个新的数据类型,带有多个成员
union tag
{
definition A;
definition B;
...
} variables;
// tag - 共同体标签
// definition - 标准的变量定义,比如 int i;
// variables - 指定一个或多个结构变量,定义在结构的末尾
// 定义一个共同体 Data
union Data
{
// 成员可以是任何内置或自定义的数据类型
int i;
float f;
char str[20]; // 共同体占用内存 ≥ 最大成员占用内存
};
// 声明 Data 类型的变量,可以存储一个整数、或浮点数、或字符串
union Data data;
使用成员访问运算符 .
访问共用体的成员,同一时间只能有一个成员带有值。
#include
#include
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
/* 访问 data 共同体成员,赋值并输出 */
data.i = 10;
printf( "data.i : %d\n", data.i); // 赋值 i 后立即输出
data.f = 220.5; // 赋值给 f ,则 i 损坏
printf( "data.f : %f\n", data.f); // 赋值 f 后立即输出
strcpy( data.str, "C Programming"); // 赋值给 str ,则 f 损坏
printf( "data.str : %s\n", data.str); // 赋值 str 后立即输出
return 0;
}
// 每一次赋值占用内存位置,会导致其他赋值损坏,所以每个变量要立即使用
有些信息在存储时不需要占用一个完整的字节,而只需占几个或一个二进制位,为了节省存储空间和处理简便,C 语言提供了"位域"或"位段"的数据结构。
关联知识点:17、结构体
把一个字节的二进位划为几个不同的区域,每个区域有域名和位数,就可以把几个不同的对象用一个字节的二进制位域来表示;如果一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。
// 定义一个位域结构
struct tag
{
// 带有预定义宽度的变量被称为位域,可以存储多于 1 位的数
type realm_var1 : width;
type realm_var2 : width;
...
};
// type - 只能是 int、unsigned int、signed int 三种类型
// realm_var - 位域(变量)的名称
// width - 位域的宽度,不能超过所属数据类型的长度
// 定义宽度为 3 位的位域变量来存储 0~7 的值(8 的二进制 1000 有4位)
struct
{
unsigned int age : 3; // 不可使用超过 3 位的值,否则编译器警告
} Age;
// 使用无名位域表示从下一单元开始存放
struct bs{
unsigned a:4; // a 占第一字节的 4 位
unsigned :4; // 空域,不能使用(0000)
unsigned b:4; // b 从第二字节开始占用 4 位
unsigned c:4; // c 占用 4 位
}
// 位域在本质上就是一种结构类型,不过其成员是按二进位分配的
#include
int main(){
// 声明类型为 bs 的变量 bit 和指针 *pbit
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;
pbit->a = 0;
pbit->b &= 3; // pbit->b = pbit->b & 3
pbit->c |= 1; // pbit->c = pbit->c | 1
// 访问指针位域并输出
printf("%d,%d,%d\n", pbit->a, pbit->b, pbit->c);
}
为内置的或自定义的数据类型取一个新的名字,可代替使用,通常为大写字母。
typedef
关键字// 为 unsigned char 定义别名 BYTE,可代替其进行编译
typedef unsigned char BYTE;
BYTE b1, b2;
#include
#include
// 为结构体 Books 定义类型别名 Book
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} Book;
int main( )
{
// 使用类型别名 Book 声明变量
Book book;
strcpy( book.title, "C 教程");
book.book_id = 12345;
printf( "书标题 : %s\n", book.title);
printf( "书 ID : %d\n", book.book_id);
return 0;
}
#define
预处理指令关联知识点:23.1 预处理器
#include
// 定义用文本 BYTE 代替 unsigned char 进行编写,预处理器将自动换回
#define BYTE unsigned char
#define INTERGE int
// 定义常量值
#define TRUE 1
#define FALSE 0
int main( )
{
unsigned INTERGE n; // #define 定义的别名可以使用类型修饰符
printf( "TRUE 的值: %d\n", TRUE);
printf( "FALSE 的值: %d\n", FALSE);
return 0;
}
C 语言把所有的设备都当作文件,所以设备被处理的方式与文件相同。
标准文件 | 文件指针(访问文件的方式) | 设备 |
---|---|---|
标准输入 | stdin | 键盘 |
标准输出 | stdout | 屏幕 |
标准错误 | stderr | 用户屏幕 |
函数 | 说明 |
---|---|
int getchar(void) |
从标准输入 stdin 获取一个字符,转换为 int 返回读取的字符 |
int putchar(int char) |
把 char 指定的字符写入到标准输出 stdout ,转换为 int 返回该字符 |
char *gets(char *str) |
从标准输入 stdin 读取一行,并把它存储在 str 字符串中,返回 str |
int puts(const char *str) |
把字符串 str 加一个换行符输出到 stdout,返回一个非负值 |
int scanf(const char *format, ...) |
从标准输入流 stdin 读取输入,并根据提供的 format 来浏览输入 |
int printf(const char *format, ...) |
把输出写入到标准输出流 stdout ,并根据提供的 format 产生输出 |
#include
int main()
{
int c;
printf( "Enter a value :"); // 等待用户输入文本
c = getchar( ); // 输入后回车,读取一个字符
printf( "\nEntered: ");
putchar( c ); // 输出这个字符
char str[100];
printf( "Enter a value :"); // 等待用户输入文本
gets( str ); // 输入后回车,读取一行字符串(换行符终止)
printf( "\nEntered: ");
puts( str ); // 输出这行字符串
float f;
printf("Enter a number: "); // 等待用户输入文本
scanf("%f", &f); // 输入后回车,读取匹配格式的数据(空格终止)
printf("Value = %f", f); // 按格式输出这个数据
return 0;
}
%[flags][width][.precision][length]specifier
规定符 | 说明 |
---|---|
%d |
十进制有符号整数 |
%u |
十进制无符号整数 |
%f |
浮点数 |
%s |
字符串 |
%c |
单个字符 |
%p |
指针的值 |
%e |
指数形式的浮点数 |
%x %X |
无符号以十六进制表示的整数 |
%o |
无符号以八进制表示的整数 |
%g |
把输出的值按照 %e 或者 %f 类型中输出长度较小的方式输出 |
%p |
输出地址符 |
%lu |
32位无符号整数 |
%llu |
64位无符号整数 |
%% |
输出百分号字符本身 |
%-10s |
左对齐并占用宽度为 10 的字符串 |
%5.2f |
右对齐并占用宽度为 5,保留两位小数的浮点数 |
%#x |
输出带有 0x 前缀的十六进制数 |
C++ 的 I/O 发生在流中,流是字节序列。
头文件 | 函数和描述 |
---|---|
该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。 | |
该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务。 | |
该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。 |
预定义的对象 cout
是 iostream 类的一个实例,与流插入运算符 <<
结合使用;cout 对象"连接"到标准输出设备(显示屏)。
#include
using namespace std;
int main( )
{
char str[] = "Hello C++";
cout << "Value of str is : " << str << endl;
}
// 流插入运算符 << 显示值,被重载来输出内置类型(整型、浮点型、double 型、字符串和指针)的数据项,在一个语句中可以多次使用
预定义的对象 cin
是 iostream 类的一个实例,与流提取运算符 >>
结合使用;cin 对象附属到标准输入设备(键盘)。
#include
using namespace std;
int main( )
{
char name[50];
cout << "请输入您的名称: "; // 提示用户输入名称
cin >> name; // cin>>提取用户输入的值并存储在name中
cout << "您的名称是: " << name << endl;
}
// 流提取运算符 >> 提取值并把它存储在给定的变量中,在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:
cin >> name >> age;
预定义的对象 cerr
是 iostream 类的一个实例,与流插入运算符 <<
结合使用;cerr 对象附属到标准输出设备(显示屏),但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。
#include
using namespace std;
int main( )
{
char str[] = "Unable to read....";
cerr << "Error message : " << str << endl;
}
预定义的对象 clog
是 iostream 类的一个实例,与流插入运算符 <<
结合使用;clog 对象附属到标准输出设备(显示屏),但 clog 对象是缓冲的(每个流插入 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出)。
#include
using namespace std;
int main( )
{
char str[] = "Unable to read....";
clog << "Error message : " << str << endl;
}
【好习惯】使用 cerr 流来显示错误消息,其他的消息使用 clog 流输出!
一个文件,无论它是文本文件还是二进制文件,都是代表了一系列的字节,C 语言提供了访问顶层的函数,和底层(OS)调用来处理存储设备上的文件。
fopen()
- 使用给定的模式 mode 打开 filename 所指向的文件FILE *fopen(const char *filename, const char *mode);
// filename - 字符串,表示要打开的文件名称
// mode - 字符串,表示文件的访问模式,可以是以下表格中的值
访问模式 | 二进制 | 描述 |
---|---|---|
r |
"rb" |
打开一个已有的文本文件,允许读取文件 |
w |
"wb" |
打开一个文本文件,截断为零长度,从文件的开头重新写入;如果文件不存在,则会创建一个新文件 |
a |
"ab" |
打开一个文本文件,以追加模式写入文件;如果文件不存在,则会创建一个新文件 |
r+ |
"rb+" "r+b" |
打开一个文本文件,允许读写文件 |
w+ |
"wb+" "w+b" |
打开一个文本文件,截断为零长度,重新读写文件;如果文件不存在,则会创建一个新文件 |
a+ |
"ab+" "a+b" |
打开一个文本文件,从文件的开头开始读取,或者以追加模式写入;如果文件不存在,则会创建一个新文件 |
fclose()
- 关闭流 stream,刷新所有的缓冲区int fclose(FILE *stream);
// stream - 指向 FILE 对象的指针,该 FILE 对象指定了要被关闭的流
fputc()
- 把参数 char 字符写入到流 stream 中,并把位置标识符往前移动int fputc(int char, FILE *stream);
// char - 要被写入的字符,该字符以其对应的 int 值进行传递
// stream - 指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流
fprintf()
- 发送格式化输出到流 stream 中int fprintf(FILE *stream, const char *format, ...);
// stream - 指向 FILE 对象的指针,该 FILE 对象标识了流
// format - 格式化字符串,包含嵌入的 format 标签
fgetc()
- 从指定的流 stream 获取下一个字符,并把位置标识符往前移动int fgetc(FILE *stream);
// stream - 指向 FILE 对象的指针,该 FILE 对象标识了要执行操作的流
fgets()
- 从指定的流 stream 获取下一个字符,并把位置标识符往前移动char *fgets(char *str, int n, FILE *stream);
// str - 指向一个字符数组的指针,该数组存储了要读取的字符串
// n - 要读取的最大字符数(包括\0),通常是使用以 str 传递的数组长度
// stream - 指向 FILE 对象的指针,该 FILE 对象标识了要读取字符的流
fscanf()
- 从流 stream 读取格式化输入int fscanf(FILE *stream, const char *format, ...);
// stream - 指向 FILE 对象的指针,该 FILE 对象标识了流
// format - 格式化字符串,包含空格字符、非空格字符和 format 说明符
fread()
- 从给定流 stream 读取数据到 ptr 所指向的数组中size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
// ptr - 指向带有最小尺寸 size*nmemb 字节的内存块的指针
// size - 要读取的每个元素的大小,以字节为单位
// nmemb - 元素的个数,每个元素的大小为 size 字节
// stream - 指向 FILE 对象的指针,该 FILE 对象指定了一个输入流
fwrite()
- 把 ptr 所指向的数组中的数据写入到给定流 stream 中size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
// ptr - 指向要被写入的元素数组的指针
// size - 要被写入的每个元素的大小,以字节为单位
// nmemb - 元素的个数,每个元素的大小为 size 字节
// stream - 指向 FILE 对象的指针,该 FILE 对象指定了一个输出流
从文件读取流和向文件写入流。
数据类型 | 描述 |
---|---|
ofstream |
输出文件流,用于创建文件并向文件写入信息 |
ifstream |
输入文件流,用于从文件读取信息 |
fstream |
文件流,可以创建文件,向文件写入信息,并从文件读取信息 |
open()
- fstream、ifstream 和 ofstream 对象的一个成员void open(const char *filename, ios::openmode mode);
// *filename - 指定要打开的文件的名称和位置
// mode - 定义文件被打开的模式
模式标志 | 描述 |
---|---|
ios::app |
追加模式,所有写入都追加到文件末尾 |
ios::ate |
文件打开后定位到文件末尾 |
ios::in |
打开文件用于读取 |
ios::out |
打开文件用于写入 |
ios::trunc |
如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0 |
/* 以写入模式打开文件,并希望截断文件,以防文件已存在 */
ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );
/* 打开一个文件用于读写 */
ifstream afile;
afile.open("file.dat", ios::out | ios::in );
程序终止时会自动刷新所有流,释放所有分配的内存,并关闭所有打开的文件。
【好习惯】在程序终止前主动关闭所有打开的文件!
close()
- fstream、ifstream 和 ofstream 对象的一个成员void close();
<<
向文件写入信息>>
从文件读取信息#include
#include
using namespace std;
int main () {
char data[100];
/* 以写模式打开文件 */
ofstream outfile;
outfile.open("afile.dat");
cout << "Writing to the file" << endl;
cout << "Enter your name: ";
cin.getline(data, 100); // getline()从外部读取一行
outfile << data << endl; // 向文件写入用户输入的数据
cout << "Enter your age: ";
cin >> data;
cin.ignore(); // ignore()忽略之前读取留下的多余字符
outfile << data << endl; // 再次向文件写入用户输入的数据
outfile.close(); // 关闭打开的文件
/* 以读模式打开文件 */
ifstream infile;
infile.open("afile.dat");
cout << "Reading from the file" << endl;
infile >> data; // 从文件读取数据
cout << data << endl;
infile >> data; // 再次从文件读取数据
cout << data << endl;
infile.close(); // 关闭打开的文件
return 0;
}
文件位置指针是一个整数值,指定了从文件的起始位置到指针所在位置的字节数。
seekg()
/seekp()
- 第一个参数通常是长整型;第二个参数指定查找方向:ios::beg(默认的,从流的开头开始定位)、 ios::cur(从流的当前位置开始定位)、 ios::end(从流的末尾开始定位)// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
预处理器只是一个文本替换工具,指示编译器在实际编译之前完成所需的预处理,所有的预处理器命令都以 # 开头,从第一行开始书写。
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
#include // 从系统库中获取 stdio.h 并添加到当前源文件
#include "my.h" // 从本地目录中获取 my.h 并添加到当前源文件
#define FILE_SIZE 42 // 把所有 FILE_SIZE 定义为 42
#undef FILE_SIZE // 取消已定义的 FILE_SIZE(可重定义)
// 仅当 MESSAGE 未定义时,才定义 MESSAGE
#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif
// 如果定义了 DEBUG,则执行处理语句(在编译期间随时开启或关闭调试)
#ifdef DEBUG
/* Your debugging statements here */
#endif
ANSI C 定义了许多宏,可以直接使用,但是不能修改这些预定义的宏。
宏 | 描述 |
---|---|
__DATE__ | 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。 |
__TIME__ | 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。 |
__FILE__ | 这会包含当前文件名,一个字符串常量。 |
__LINE__ | 这会包含当前行号,一个十进制常量。 |
__STDC__ | 当编译器以 ANSI 标准编译时,则定义为 1。 |
#include
main()
{
printf("File :%s\n", __FILE__ );
printf("Date :%s\n", __DATE__ );
printf("Time :%s\n", __TIME__ );
printf("Line :%d\n", __LINE__ );
printf("ANSI :%d\n", __STDC__ );
}
运算符 | 说明 |
---|---|
\ |
宏延续运算符:使一个太长的宏在一个单行延续下去 |
# |
字符串常量化运算符:把一个宏的参数转换为字符串常量 |
## |
标记粘贴运算符:合并两个参数,将两个独立的标记合并为一个标记 |
defined() |
defined 运算符:判断一个标识符是否已经使用 #define 定义过 |
#include
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
#define tokenpaster(n) printf("token" #n " = %d", token##n)
#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif
int main(void)
{
message_for(Carole, Debra);
int token34 = 40;
tokenpaster(34); // printf("token34 = %d", token34);
printf("Here is the message: %s\n", MESSAGE);
return 0;
}
#include
// 使用参数化的宏来模拟函数
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{
printf("Max between 20 and 10 is %d\n", MAX(10, 20));
return 0;
}
头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件引用共享,包含程序员编写的头文件和编译器自带的头文件。
【好习惯】建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件!
#include
指令引用系统或用户头文件// 使用包装器 #ifndef 包裹头文件引用,防止重复处理报错
#ifndef HEADER_FILE
#define HEADER_FILE
#include // 引用系统头文件(标准库),在系统库目录中搜索
#include "file" // 引用用户头文件(相对路径),在当前文件目录中搜索
#endif
#include
指令浏览指定的文件作为输入#include "header.h" // 在当前位置插入 header.h 文件内容
// 预处理器使用宏来定义头文件的名称,查找所有匹配宏扩展名称的文件
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H
把变量值显式地从一种类型转换为另一种数据类型。
【好习惯】只要有需要类型转换的时候都用上强制类型转换运算符!
(type_name) expression
#include
int main()
{
int sum = 17, count = 5;
double mean;
mean = (double) sum / count; // 强制类型转换运算符优先级大于除
printf("Value of mean : %f\n", mean );
}
把小于 int 或 unsigned int 的整数类型转换为 int 或 unsigned int 的过程。
#include
int main()
{
int i = 17;
char c = 'c'; /* 把 'c' 的值转换为对应的 ascii 值 99 */
int sum;
sum = i + c;
printf("Value of sum : %d\n", sum );
}
隐式地把值强制转换为相同的类型,不适用于赋值运算符、逻辑运算符( &&、||)。编译器首先执行整数提升,如果操作数类型不同,则转换为下列层次中出现的最高层次的类型:
#include
int main()
{
int i = 17;
char c = 'c'; // c 被转换为 ascii 值 99
float sum; // 计算结果应是 float 型
sum = i + c; // 常用的算术转换把 i 和 c 转换为浮点型再相加
printf("Value of sum : %f\n", sum );
}
发生错误时,程序返回 1 或 NULL,同时会设置一个错误代码 errno(头文件 errno.h ),可以通过检查返回值,然后根据返回值决定采取哪种适当的动作。
【好习惯】在程序初始化时,把 errno 设置为 0,表示程序中没有错误!
errno
- 系统调用的全局变量,表示在函数调用期间发生了错误extern int errno;
perror()
- 把一个描述性错误消息输出到标准错误 stderr 文件流void perror(const char *str);
// str - 字符串,包含了一个自定义消息,将显示在原本的错误消息之前
strerror()
- 搜索错误号 errnum,返回一个指向错误消息字符串的指针char *strerror(int errnum);
// errnum - 错误号,通常是 errno
#include
#include
#include
extern int errno;
int main ()
{
FILE * pf;
int errnum;
pf = fopen ("unexist.txt", "rb"); // 尝试打开一个不存在的文件
if (pf == NULL)
{
errnum = errno;
fprintf(stderr, "错误号: %d\n", errno);
perror("通过 perror 输出错误");
fprintf(stderr, "打开文件错误: %s\n", strerror( errnum ));
}
else
{
fclose (pf);
}
return 0;
}
程序正常退出时会带有状态值 EXIT_SUCCESS(0),如果程序中存在错误,退出程序时会带有状态值 EXIT_FAILURE(-1)。
#include
#include
main()
{
int dividend = 20;
int divisor = 5;
int quotient;
// 在进行除法运算前先检查除数是否为零
if(divisor == 0){
fprintf(stderr, "除数为 0 退出运行...\n");
exit(EXIT_FAILURE); // 错误状态值 EXIT_FAILURE
}
quotient = dividend / divisor;
fprintf(stderr, "quotient 变量的值为: %d\n", quotient );
exit(EXIT_SUCCESS);
}
在函数的定义中使用函数自身的方法。
【好习惯】定义一个从函数退出的条件,否则会进入死循环!
void recursion()
{
statements;
... ... ...
recursion(); /* 函数调用自身 */
... ... ...
}
int main()
{
recursion();
}
#include
// 使用递归函数计算一个给定的数的阶乘
double factorial(unsigned int i)
{
if(i <= 1)
{
return 1;
}
return i * factorial(i - 1);
}
// 使用递归函数生成一个给定的数的斐波那契数列
int fibonaci(int i)
{
if(i == 0)
{
return 0;
}
if(i == 1)
{
return 1;
}
return fibonaci(i-1) + fibonaci(i-2);
}
int main()
{
int i = 15;
printf("%d 的阶乘为 %f\n", i, factorial(i)); // 输出阶乘
int i;
for (i = 0; i < 10; i++)
{
printf("%d\t\n", fibonaci(i)); // 输出数列
}
return 0;
}
允许定义一个函数,能根据具体的需求接受可变数量的参数。
int func_name(int arg1, ...);
// int - 要传递的可变参数的总数
// ... - 可变参数列表
va_list valist;
va_start()
宏来初始化 va_list 变量为一个参数列表:// 初始化 ap 变量,必须在 va_arg 和 va_end 之前被调用
void va_start(va_list ap, last_arg);
// ap - va_list 类型的对象,存储 va_arg 获取额外参数时必需的信息
// last_arg - 可变参数列表之前的最后一个参数
va_arg()
宏和 va_list 变量来访问参数列表中的每个项:// 检索函数参数列表中类型为 type 的下一个参数
type va_arg(va_list ap, type);
// ap - va_list 类型的对象,存储了有关额外参数和检索状态的信息
// type - 类型名称,作为扩展自该宏的表达式的类型来使用
va_end()
来清理赋予 va_list 变量的内存:// 允许使用 va_start 宏的带有可变参数的函数返回,将 ap 置为 NULL
void va_end(va_list ap);
// ap - 之前由同一函数中的 va_start 初始化的 va_list 对象
#include
#include // stdarg.h 头文件提供了实现可变参数的函数和宏
// 定义一个带有 num 个可变参数的函数
double average(int num,...)
{
double sum = 0.0;
int i;
// 创建一个 va_list 类型的变量
va_list valist;
// 初始化可变参数列表,将 valist 指向第一个参数
va_start(valist, num);
for (i = 0; i < num; i++)
{
// 检索可变参数列表中的下一个参数,将 valist 指向下一个参数
sum += va_arg(valist, int);
}
// 结束可变参数列表的访问,清理为 valist 保留的内存
va_end(valist);
return sum/num;
}
int main()
{
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
内存通过指针变量来管理,它存储了内存地址,可以指向任何数据类型的变量。
关联知识点:7.1 运算符类型
函数或运算符 | 描述 |
---|---|
void *malloc(int num); |
分配一块指定大小的内存空间,值是未知的 |
void *calloc(int num, int size); |
分配 num 个长度为 size 的内存空间,都初始化为 0 |
void *realloc(void *ptr, int newsize); |
重新分配 ptr 所指向的内存,扩展到 newsize 大小 |
void free(void *ptr); |
释放 ptr 所指向的之前动态分配的内存空间 |
void *memcpy(void *str1, const void *str2, int n); |
从存储区 str2 复制 n 个字节到存储区 str1 |
void *memmove(void *str1, const void *str2, int n); |
从 str2 复制 n 个字符到 str1,可处理重叠的内存区域 |
sizeof() |
获取数据类型或变量的字节大小 |
* |
获取指针所指向的内存地址或变量值 |
& |
获取变量的内存地址 |
-> |
使用指针访问结构的成员 |
动态分配内存:如果预先不知道需要存储的文本长度,需要定义一个指针,指向未定义所需内存大小的字符,后续再根据需求来分配内存。
重新调整内存大小和释放内存:当程序退出时,操作系统会自动释放所有分配给程序的内存,但在不需要内存时,都应该主动调用函数 free() 来释放内存,或者通过调用函数 realloc() 来增加或减少已分配的内存块的大小。
#include
#include
#include
int main()
{
char name[100];
char *storage;
strcpy(name, "Zara Ali");
/* 分配一块 30 个 sizeof(char) 长度的内存空间 */
storage = (char *)malloc( 30 * sizeof(char) );
// storage = (char *)calloc(30, sizeof(char));
if( storage == NULL )
{
fprintf(stderr, "Error\n");
}
else
{
strcpy( storage, "Zara ali a DPS student.");
}
/* 如果想要存储更大的描述信息,需要重新分配一块更大的内存空间 */
storage = (char *)realloc(storage, 100 * sizeof(char));
if( storage == NULL )
{
fprintf(stderr, "Error\n");
}
else
{
strcat( storage, "She is in class 10th");
}
printf("Name = %s\n", name );
printf("Description: %s\n", storage );
/* 使用 free() 函数释放内存 */
free(storage);
}
// 动态分配内存时,用户有完全控制权,可以传递任何大小的值
C++ 程序中的内存分为两个部分:
有时候无法提前预知需要多少内存来存储某个变量中的信息,可以使用 new
运算符为给定类型的变量在运行时分配堆内的内存,并返回所分配的空间地址;若不再需要动态分配的内存空间,可以使用 delete
运算符删除之前分配的内存。
【好习惯】尽量不使用 malloc() 函数,new 不仅分配了内存还创建了对象
new dataType; // dataType - 任意内置的或用户自定义的数据类型
delete dataType; // 释放 dataType 所指向的内存
#include
using namespace std;
int main ()
{
// 定义一个指向 double 类型的指针,然后请求内存,在执行时分配
double *pvalue = NULL; // 初始化为空指针
pvalue = new double; // new 为变量请求内存
*pvalue = 29494.99; // 在分配的地址存储值
cout << "Value of pvalue : " << *pvalue << endl;
delete pvalue; // delete 释放之前分配的内存
return 0;
}
/* 一维数组 */
int *array = new int[m]; // 动态分配数组空间,长度为m
delete [] array; // 删除 array 所指向的数组,释放内存
/* 二维数组 */
int **array;
array = new int*[m]; // 数组第一维长度为 m(指针)
for( int i=0; i<m; i++ )
{
array[i] = new int[n]; // 数组第二维长度为 n(变量)
}
for( int i=0; i<m; i++ )
{
delete [] array[i]; // 释放单位空间
}
delete [] array; // 释放数组空间
/* 三维数组 */
int ***array;
array = new int**[m]; // 数组第一维长度为 m(指针指针)
for( int i=0; i<m; i++ )
{
array[i] = new int*[n]; // 数组第二维长度为 n(指针)
for( int j=0; j<n; j++ )
{
array[i][j] = new int[h]; // 数组第三维长度为 h(变量)
}
}
for( int i=0; i<m; i++ )
{
for( int j=0; j<n; j++ )
{
delete[] array[i][j]; // 释放三维空间
}
delete[] array[i]; // 释放二维空间
}
delete[] array; // 释放数组空间
#include
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
// 为一个包含四个 Box 对象的数组分配内存,构造函数将被调用 4 次
Box* myBoxArray = new Box[4];
// 当删除这些对象时,析构函数也将被调用相同的次数(4次)
delete [] myBoxArray;
return 0;
}
如果想从外部控制程序,可以从命令行传值,这些值称为命令行参数。
int main(int argc, char *argv[]) { }
// argc - 命令行传入参数的个数(程序名称+参数列表)
// argv[] - 指针数组(程序名称+参数列表),指向命令行传递的每个参数
#include
/* 检查命令行是否有提供参数,并根据参数执行相应的动作 */
int main( int argc, char *argv[] )
{
/* argv[0] - 存储程序的名称 */
printf("Program name %s\n", argv[0]);
if( argc == 2 ) // 程序名称 + 1个参数
{
/* argv[1] - 指向第一个命令行参数的指针 */
printf("The argument supplied is %s\n", argv[1]);
}
else if( argc > 2 ) // 程序名称 + 2个及以上的参数
{
printf("Too many arguments supplied.\n");
}
else
{
printf("One argument expected.\n");
}
}
# 使用一个参数编译
$./a.out testing
# 使用多个参数编译(空格分隔)
$./a.out testing1 testing2
$./a.out "t1 t2" testing3 # 若参数本身有空格,则放在引号内
# 不使用参数编译
$./a.out
重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。
#include
void bubble_sort(int arr[], int len) {
int i, j, temp;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
int main() {
int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = (int) sizeof(arr) / sizeof(*arr);
bubble_sort(arr, len);
int i;
for (i = 0; i < len; i++)
printf("%d ", arr[i]);
return 0;
}
在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,再从剩余未排序元素中继续寻找最小(大)元素,依次存放。
void selection_sort(int a[], int len)
{
int i,j,temp;
for (i = 0 ; i < len - 1 ; i++)
{
int min = i; // 记录最小值,第一个元素默认最小
for (j = i + 1; j < len; j++) // 访问未排序的元素
{
if (a[j] < a[min]) // 找到目前最小值
{
min = j; // 记录最小值
}
}
if(min != i)
{
temp=a[min]; // 交换两个变量
a[min]=a[i];
a[i]=temp;
}
/* swap(&a[min], &a[i]); */ // 使用自定义函数交換
}
}
/*
void swap(int *a,int *b) // 交换两个变量
{
int temp = *a;
*a = *b;
*b = temp;
}
*/
构建有序序列,在已排序序列中从后向前扫描,把已排序元素逐步向后挪位,为未排序数据找到相应位置并插入,通常采用in-place排序。
void insertion_sort(int arr[], int len){
int i,j,temp;
for (i=1;i<len;i++){
temp = arr[i];
for (j=i;j>0 && arr[j-1]>temp;j--)
arr[j] = arr[j-1];
arr[j] = temp;
}
}
也称递减增量排序算法,是非稳定排序算法,是插入排序的一种更高效的改进版本。
void shell_sort(int arr[], int len) {
int gap, i, j;
int temp;
for (gap = len >> 1; gap > 0; gap = gap >> 1)
for (i = gap; i < len; i++) {
temp = arr[i];
for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap)
arr[j + gap] = arr[j];
arr[j + gap] = temp;
}
}
把数据分为两段,从两段中逐个选最小的元素移入新数据段的末尾,可从上到下或从下到上进行。
// 迭代法
int min(int x, int y) {
return x < y ? x : y;
}
void merge_sort(int arr[], int len) {
int* a = arr;
int* b = (int*) malloc(len * sizeof(int));
int seg, start;
for (seg = 1; seg < len; seg += seg) {
for (start = 0; start < len; start += seg + seg) {
int low = start, mid = min(start + seg, len), high = min(start + seg + seg, len);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
while (start1 < end1 && start2 < end2)
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
while (start1 < end1)
b[k++] = a[start1++];
while (start2 < end2)
b[k++] = a[start2++];
}
int* temp = a;
a = b;
b = temp;
}
if (a != arr) {
int i;
for (i = 0; i < len; i++)
b[i] = a[i];
b = a;
}
free(b);
}
// 递归法
void merge_sort_recursive(int arr[], int reg[], int start, int end) {
if (start >= end)
return;
int len = end - start, mid = (len >> 1) + start;
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
merge_sort_recursive(arr, reg, start1, end1);
merge_sort_recursive(arr, reg, start2, end2);
int k = start;
while (start1 <= end1 && start2 <= end2)
reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
while (start1 <= end1)
reg[k++] = arr[start1++];
while (start2 <= end2)
reg[k++] = arr[start2++];
for (k = start; k <= end; k++)
arr[k] = reg[k];
}
void merge_sort(int arr[], const int len) {
int reg[len];
merge_sort_recursive(arr, reg, 0, len - 1);
}
在区间中随机挑选一个元素作基准,将小于基准的元素放在基准之前,大于基准的元素放在基准之后,再分别对小数区与大数区进行排序。
// 迭代法
typedef struct _Range {
int start, end;
} Range;
Range new_Range(int s, int e) {
Range r;
r.start = s;
r.end = e;
return r;
}
void swap(int *x, int *y) {
int t = *x;
*x = *y;
*y = t;
}
void quick_sort(int arr[], const int len) {
if (len <= 0)
return; // 避免len等於負值時引發段錯誤(Segment Fault)
// r[]模擬列表,p為數量,r[p++]為push,r[--p]為pop且取得元素
Range r[len];
int p = 0;
r[p++] = new_Range(0, len - 1);
while (p) {
Range range = r[--p];
if (range.start >= range.end)
continue;
int mid = arr[(range.start + range.end) / 2]; // 選取中間點為基準點
int left = range.start, right = range.end;
do
{
while (arr[left] < mid) ++left; // 檢測基準點左側是否符合要求
while (arr[right] > mid) --right; //檢測基準點右側是否符合要求
if (left <= right)
{
swap(&arr[left],&arr[right]);
left++;right--; // 移動指針以繼續
}
} while (left <= right);
if (range.start < right) r[p++] = new_Range(range.start, right);
if (range.end > left) r[p++] = new_Range(left, range.end);
}
}
// 递归法
void swap(int *x, int *y) {
int t = *x;
*x = *y;
*y = t;
}
void quick_sort_recursive(int arr[], int start, int end) {
if (start >= end)
return;
int mid = arr[end];
int left = start, right = end - 1;
while (left < right) {
while (arr[left] < mid && left < right)
left++;
while (arr[right] >= mid && left < right)
right--;
swap(&arr[left], &arr[right]);
}
if (arr[left] >= arr[end])
swap(&arr[left], &arr[end]);
else
left++;
if (left)
quick_sort_recursive(arr, start, left - 1);
quick_sort_recursive(arr, left + 1, end);
}
void quick_sort(int arr[], int len) {
quick_sort_recursive(arr, 0, len - 1);
}