C语言复习
https://www.bilibili.com/video/BV1cq4y1U7sg?p=1&vd_source=594d36a0860080a36fe599e0b84e5fb2
必须实践敲代码 (不是抄写 ! 抄写没有意义)
理清别人代码的思路
不看别人的代码, 按照代码的思路, 自己写代码
写注释 ! 写注释 ! 写注释 !
必须画图, 理解内存布局
要不断调试
多写, 多刷题
一、初识C语言 1.1 第一个C语言程序 1 2 3 4 5 6 7 8 9 10 #include <stdio.h> int main () { printf ("hello world" ); return 0 ; }
1.2 C语言数据类型
char 字符数据类型
short 短整型
int 整型
long 长整型
long long 长长整型
float 单精度浮点数
double 双精度浮点数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main () { printf ("%d\n" , sizeof (char )); printf ("%d\n" , sizeof (short )); printf ("%d\n" , sizeof (int )); printf ("%d\n" , sizeof (long )); printf ("%d\n" , sizeof (long long )); printf ("%d\n" , sizeof (float )); printf ("%d\n" , sizeof (double )); return 0 ; }
输出结果 (单位: 字节)
计算机中的单位
bit 比特位
byte 字节 1 byte = 8 bit
KB 1 KB = 1024 byte
MB 1 MB = 1024 KB
GB 1 GB = 1024 MB
TB 1 TB = 1024 GB
PB 1 PB = 1024 TB
1.3 常量&变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main () { int age = 20 ; double weight = 80 ; age = age + 1 ; printf ("%d\n" , age); printf ("%lf\n" , weight); return 0 ; }
局部变量&全局变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int a = 100 ;int main () { int a = 10 ; printf ("%d\n" , a); 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 24 25 26 27 28 29 30 31 32 int main () { int a = 0 ; int b = 0 ; int sum = 0 ; scanf ("%d %d" , &a, &b); sum = a + b; printf ("sum = %d\n" , sum); return 0 ; } #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main () { int a = 0 ; int b = 0 ; int sum = 0 ; scanf ("%d %d" , &a, &b); sum = a + b; printf ("sum = %d\n" , sum); return 0 ; }
变量的作用域和生命周期 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int year = 2022 ; int main () { printf ("年: %d\n" , year); { int month = 12 ; printf ("月: %d\n" , month); } 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 24 25 26 27 28 29 30 31 32 #define 一万 10000; enum Ocean { PACIFIC, INDIAN_OCEAN, ATLANTIC, ARCTIC_OCEAN }; int main () { 12345 ; 'a' ; "abcsde" ; const int num = 123 ; int one_h = 一万; printf ("%d\n" , one_h); enum Ocean place = ATLANTIC; printf ("%d\n" , PACIFIC); printf ("%d\n" , INDIAN_OCEAN); printf ("%d\n" , ATLANTIC); printf ("%d\n" , ARCTIC_OCEAN); return 0 ; }
输出:
1.4 字符串
字符串就是一串字符 (用 "" 扩起来的字符)
字符串在结尾的位置隐藏了一个 \0 字符作为字符串的结束标志,
通过 strlen() 函数字符串长度时不算在里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <string.h> int main () { char array [] = "hello" ; char array2[] = { 'h' , 'e' , 'l' , 'l' , 'o' }; printf ("%d\n" , strlen (array )); printf ("%d\n\n" , strlen (array2)); printf ("%s\n" , array ); printf ("%s\n\n" , array2); char array3[] = {'h' , 'e' , 'l' , 'l' , 'o' , '\0' }; printf ("%d\n" , strlen (array )); printf ("%d\n\n" , strlen (array3)); printf ("%s\n" , array ); printf ("%s\n\n" , array3); return 0 ; }
输出:
1 2 3 4 5 6 7 8 9 10 11 5 65 hello hello烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫虁鯵? 5 5 hello hello
1.5 转义字符
转义字符 释义
?
在书写连续多个问号时使用,防止他们被解析成三字母词
\‘
用于表示字符常量’
\“
用于表示一个字符串内部的双引号
\\
用于表示一个反斜杠,防止它被解释为一个转义序列符。
\a
警告字符,蜂鸣
\b
退格符
\f
进纸符
\n
换行
\r
回车
\t
水平制表符
\v
垂直制表符
\ddd
ddd 表示1~3个八进制的数字。 如: \130 X
\xdd
dd 表示2个十六进制数字。 如: \x30 0
1.6 选择语句 1 2 3 4 5 6 7 8 9 10 11 int main () { int complete = 0 ; printf ("今天的任务完成了吗? ( 1 or 0 ) :> " ); scanf ("%d" , &complete); if (complete == 1 ) printf ("恭喜你!!!" ); else printf ("不要摆啊!!!" ); return 0 ; }
1.7 循环语句 1 2 3 4 5 6 7 8 9 10 11 int main () { int age = 0 ; while (age < 100 ) { printf ("You are alive, %d\n" , age); age++; } if (age = 100 ) printf ("You died" ); return 0 ; }
1.8 函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int multiply (int a, int b) { int c = 0 ; c = a * b; return c; } int main () { int num1 = 12 ; int num2 = 13 ; int sum = 0 ; sum = multiply(num1, num2); printf ("%d" , sum); return 0 ; }
1.9 数组 1 2 3 4 5 6 7 8 9 10 11 12 13 int main () { int array_int[3 ] = { 1 , 2 , 3 }; char array_char[5 ] = { 'a' , 'b' , 'c' }; for (int i = 0 ; i < 3 ; i++) printf ("%d " , array_int[i]); printf ("\n" ); for (int j = 0 ; j < 5 ; j++) { printf ("%c " , array_char[j]); } printf ("end" ); return 0 ; }
输出:
1.10 操作符
整数的二进制表示
原码
反码 – 符号位不变, 其余位置按位取反
补码 (整数在内存中的存储) – 反码 + 1
1 2 3 4 5 6 7 8 9 10 11 /* 整数二进制共 4 * 8 = 32 位 (整型 4 个字节) 最高位表示符号位 0 表示正数, 1 表示负数 正整数的原码、反码、补码相同(规定) */ -1: 10000000000000000000000000000001 (原码) 11111111111111111111111111111110 (反码) 11111111111111111111111111111111 (补码)
算数操作符
+
-
*
/
%
移位操作符 (移动二进制位)
左移运算符: << 低位补 0
右移运算符: >>
位操作符
按位与: &
按位或: |
按位异或: ^
赋值操作符
=
+
=
-=
*=
/=
&=
^=
|=
>>=
<<=
单目操作符 (只有一个操作数)
操作符
作用
!
逻辑反操作
-
负值
+
正值
&
取地址
sizeof
操作数的类型长度(以字节为单位)
~
对一个数的二进制按位取反
–
前置、后置–
++
前置、后置++
*
间接访问操作符(解引用操作符)
(类型)
强制类型转换
关系操作符
>
>=
<
<=
!=
==
逻辑操作符
&&
||
条件操作符 (三目运算符)
exp1 ? exp2 : exp3
如果 exp1 为 true, 表达式的值为 exp2; 为 false, 表达式的值为 exp3
逗号表达式
exp1, exp2, exp3 ... expN
从左往右运算, 逗号表达式的值为最后一个表达式 expN 的值
下标引用
[]
函数调用
()
结构成员
.
->
1.11 常见关键字 1 2 3 4 5 6 7 8 9 10 11 12 13 14 auto 自动创建, 自动销毁, 所有局部变量都是auto break case char const continue default do double else enum extern 用来生命外部符号 float for goto if int long register 寄存器关键字 (大量频繁被使用的数据, 放在寄存器中提高效率) return short signed 有符号的 unsigned 无符号的 sizeof static 静态的 struct switch typedef union 联合体 (共用体) void 空 volatile while
1 2 3 4 5 6 7 typedef int t_int;int main () { t_int a = 1 ; 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 24 25 26 27 28 29 30 31 32 33 34 35 36 void test_common () { int i = 0 ; i++; printf ("%d" , i); } void test_static () { static int i = 0 ; i++; printf ("%d" , i); } int main () { int i = 0 ; for (i = 0 ; i < 10 ; i++) { test_common(); } printf ("\n\n" ); for (i = 0 ; i < 10 ; i++) { test_static(); } return 0 ; }
输出:
1.12 define 定义常量和宏 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define 一坤年 2.5 #define Multiply(x, y) ((x)*(y)) int main () { double sum = Multiply(一坤年, 一坤年); printf ("%lf\n" , sum); printf ("%lf\n" , 10 * sum); return 0 ; }
输出:
1.13 指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main () { int a = 3 ; int * indicator_a = &a; char b = 'a' ; char * indicator_b = &b; *indicator_a = 10 ; printf ("%d\n" , a); int num = 10 ; int * p; p = # *p = num; return 0 ; }
指针变量的大小
指针需要多达空间, 取决于地址存储需要多达空间
32位机 32 bit – 4 byte
64位机 64 bit – 8 byte
1 2 3 4 5 6 7 8 int main () { printf ("%d\n" , sizeof (char *)); printf ("%d\n" , sizeof (short *)); printf ("%d\n" , sizeof (int *)); printf ("%d\n" , sizeof (double *)); return 0 ; }
输出:
1.14 结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct Garden { char name[20 ]; int num; double money; }; int main () { struct Garden flower = { "violet" , 100 , 33.3 }; printf ("一般写法: %s, %d, %lf\n" , flower.name, flower.num, flower.money); struct Garden * pf = &flower; printf ("指针.写法: %s, %d, %lf\n" , (*pf).name, (*pf).num, (*pf).money); printf ("指针->写法: %s, %d, %lf\n" , pf->name, pf->num, pf->money); return 0 ; }
二、分支和循环
分支语句
循环语句
转向语句
goto 语句
break 语句
continue 语句
return 语句
2.1 分支语句 (选择结构)
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 int main () { printf ("请输入一个数: " ); int num = 0 ; scanf ("%d" , &num); if (num % 2 == 0 ) { printf ("\n这个数不是奇数" ); } else { printf ("\n这个数是奇数" ); } return 0 ; } int main () { for (int i = 1 ; i < 101 ; i++) { if (i % 2 != 0 ) { printf ("%d\n" , i); } } 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 24 25 26 27 28 29 30 31 32 33 int main () { int day = 0 ; scanf ("%d" , &day); switch (day) { case 1 : printf ("今天是星期1" ); break ; case 2 : printf ("今天是星期2" ); break ; case 3 : printf ("今天是星期3" ); break ; case 4 : printf ("今天是星期4" ); break ; case 5 : printf ("今天是星期5" ); break ; case 6 : printf ("今天是星期6" ); break ; case 7 : printf ("今天是星期日" ); break ; default : printf ("没有这个星期哦" ); break ; } 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 24 25 int main () { int day = 0 ; scanf ("%d" , &day); switch (day) { case 1 : case 2 : case 3 : case 4 : case 5 : printf ("今天是weekday" ); break ; case 6 : case 7 : printf ("今天是weekend" ); break ; default : printf ("没有这个星期哦" ); break ; } return 0 ; }
2.2 循环语句
getchar 详见CSDN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main () { int input = 0 ; while ((input = getchar()) != EOF) { putchar (input); } return 0 ; }
输出:
1 2 3 4 5 6 7 8 9 10 11 12 12 12 fsdf fsdf fadsfa dfsdfsd fadsfa dfsdfsd fdsfa dfsdfaf\n fdsfa dfsdfaf\n dfdsaafdsf xbnnc gnbnn\n\n\n\n dfdsaafdsf xbnnc gnbnn\n\n\n\n ^Z
1 2 3 4 5 6 7 8 9 10 11 12 int main () { int num = '\0' ; while ((num = getchar()) != EOF) { if (num < '0' || num > '9' ) { continue ; } putchar (num); } return 0 ; }
输出:
1 2 3 4 5 6 7 8 f fsdf 45646 4564643 43 gfdgg 23 23
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 int main () { int i = 0 ; int j = 0 ; int count = 0 ; for (; i < 10 ; i++) { count++; } printf ("%d\n" , count); for (;;) { count++; } printf ("%d\n" , count); for (i = 0 , count = 0 ; i < 10 ; i++) { for (j = 0 ; j < 10 ; j++) { count++; } } printf ("%d\n" , count); for (i = 0 , j = 0 , count = 0 ; i < 2 && j < 5 ; i++, j++) { count++; } printf ("%d\n" , count); int i = 0 ; int k = 0 ; for (i = 0 ,k = 0 ; k = 0 ; i++, k++) { k++; } 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 24 25 26 27 28 29 30 ``` **** ## 2.3 练习 ### 计算 n 的阶乘 ```c int main () { int n = 0 ; scanf ("%d" , &n); int sum = 1 ; while (n > 0 ) { sum = sum * n; n--; } printf ("%d" , sum); return 0 ; }
计算 1!+2!+3!+……+10! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int main () { int n = 10 ; int sum_p = 0 ; while (n > 0 ) { int sum_m = 1 ; for (int i = n; i > 0 ; i--) { sum_m = sum_m * i; } sum_p = sum_m + sum_p; n--; } printf ("%d" , sum_p); return 0 ; }
在一个有序数组中查找具体的某个数字n (二分查找) 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 int main () { int arr[] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; int left = 0 ; int right = sizeof (arr)/sizeof (arr[0 ]) - 1 ; int key = 9 ; int mid = 0 ; while (left <= right) { mid = (left + right) / 2 ; if (arr[mid] > key) { right = mid - 1 ; } else if (arr[mid] < key) { left = mid + 1 ; } else { break ; } } if (left <= right) { printf ("yes" ); } else { printf ("no" ); } return 0 ; }
编写代码,演示多个字符从两端移动,向中间汇聚 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main () { char arr[] = "This is a string , I will full it with '$'." ; int left = 0 ; int right = strlen (arr) - 1 ; while (left <= right) { arr[left] = '$' ; arr[right] = '$' ; left++; right--; printf ("%s\n" , arr); } return 0 ; }
编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录成,如果三次均输入错误,则退出程序) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int main () { int password = 123456 ; int count = 0 ; while (1 ) { int input = 0 ; printf ("请输入密码: " ); scanf ("%d" , &input); if (input == password) { printf ("\n登陆成功" ); break ; } count++; if (count == 3 ) { printf ("\n登陆失败" ); break ; } } return 0 ; }
三、函数 3.1 函数分类 库函数 库函数网站
IO函数
字符串操作函数
字符操作函数
内存操作函数
时间/日期函数
数学函数
其他库函数
自定义函数 (形参&实参, 传值&传址) 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 #include <stdio.h> void Swap1 (int x, int y) { int tmp = 0 ; tmp = x; x = y; y = tmp; } void Swap2 (int *px, int *py) { int tmp = 0 ; tmp = *px; *px = *py; *py = tmp; } int main () { int num1 = 1 ; int num2 = 2 ; Swap1(num1, num2); printf ("Swap1::num1 = %d num2 = %d\n" , num1, num2); Swap2(&num1, &num2); printf ("Swap2::num1 = %d num2 = %d\n" , num1, num2); return 0 ; }
3.2 函数练习 判断一个数是不是素数 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 #include <math.h> void PrimeNumber (int n) { int whether = 0 ; if (n > 2 ) { for (int i = 2 ; i < sqrt (n); i++) { if (n % i == 0 ) { printf ("\n这个数不是素数" ); whether = 1 ; break ; } } } else { printf ("\n这个数不是素数" ); whether = 1 ; } if (whether == 0 ) { printf ("\n这个数是素数" ); } } int main () { int n = 0 ; scanf ("%d" , &n); PrimeNumber(n); 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 24 25 void LeapYear (int year) { if (year % 4 == 0 && year % 100 != 0 ) { printf ("\n这一年是闰年" ); } else if (year % 400 == 0 ) { printf ("\n这一年是闰年" ); } else { printf ("\n这一年不是闰年" ); } } int main () { int year = 0 ; scanf ("%d" , &year); LeapYear(year); 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 24 25 26 27 28 29 30 31 void Search (int * arr, int key, int left, int right) { while (left <= right) { int mid = (right + left) / 2 ; if (arr[mid] > key) { right = mid - 1 ; } else if (arr[mid] < key) { left = mid + 1 ; } else { break ; } } if (left <= right) { printf ("查找成功" ); } else { printf ("查找失败" ); } } int main () { int arr[] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; int key = 9 ; int left = 0 ; int right = sizeof (arr)/sizeof (arr[0 ]) - 1 ; Search(arr, key, left, right); return 0 ; }
写一个函数,每调用一次这个函数,就会将 num 的值增加1 1 2 3 4 5 int Count () { count++; return count; }
3.3 函数的嵌套调用&链式访问 嵌套调用
函数之中调用另一个函数
链式访问
把一个函数的返回值作为另外一个函数的参数
3.4 函数的声明&定义
函数声明
告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
函数的声明一般出现在函数的使用之前。要满足先声明后使用 。
函数的声明一般要放在头文件中的, 其他文件引用时用 include 引用
函数定义
函数的定义是指函数的具体实现,交待函数的功能实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #pargma comment(lib, "add.lib" ) #include "add.h" int Add (int x, int y) { return x + y; } int main () { int a = 1 ; int b = 2 ; int Add (int x, int y) ; int c = Add(a, b); printf ("%d" , c); return 0 ; }
3.5 函数递归
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,
它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
递归策略
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
递归的两个必要条件
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> void print (int n) { if (n>9 ) { print(n/10 ); } printf ("%d " , n%10 ); } int main () { int num = 1234 ; print(num); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #incude <stdio.h> int Strlen (const char * str) { if (*str == '\0' ) return 0 ; else return 1 +Strlen(str+1 ); } int main () { char *p = "abcdef" ; int len = Strlen(p); printf ("%d\n" , len); return 0 ; }
1 2 3 4 5 6 7 8 int factorial (int n) { if (n <= 1 ) return 1 ; else return n * factorial(n-1 ); }
1 2 3 4 5 6 7 8 int fib (int n) { if (n <= 2 ) return 1 ; else return fib(n - 1 ) + fib(n - 2 ); }
递归的改进
将递归改写成非递归。
使用 static 对象替代 nonstatic 局部对象。
在递归函数设计中,可以使用 static 对象替代 nonstatic 局部对象(即栈对象),
这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,
而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
1 2 3 4 5 6 7 8 9 10 11 int factorial (int n) { int result = 1 ; while (n > 1 ) { result *= n ; n -= 1 ; } return result; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int fib (int n) { int result; int pre_result; int next_older_result; result = pre_result = 1 ; while (n > 2 ) { n -= 1 ; next_older_result = pre_result; pre_result = result; result = pre_result + next_older_result; } return result; }
许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
经典题目
四、数组 4.1 一维数组
一维数组在内存中是连续存放的
随着下标增长, 地址由低到高变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main () { int n = 5 ; int arr[3 ] = { 1 , 2 , 3 }; int arr_three[3 ] = { 1 , 2 }; int arr_t [] = { 1 , 2 , 3 }; char arr1[4 ] = { 'f' , 'o' , 'u' , 'r' }; char arr2[] = { 'f' , 'o' , 'u' , 'r' }; char arr3[] = "four" ; int sz = sizeof (arr) / sizeof (arr[0 ]); return 0 ; }
4.2 二维数组
1 2 3 4 5 6 7 8 9 int main () { int arr1[3 ][4 ] = { 1 ,2 ,3 ,4 }; int arr2[3 ][4 ] = { {1 ,2 },{4 ,5 } }; int arr3[][4 ] = { {2 ,3 },{4 ,5 } }; return 0 ; }
4.3 数组越界
数组的下标是有范围限制的。
数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
数组名是数组首元素地址:
sizeof(数组名) - 数组名表示整个数组, 计算的是整个儿数组大小, 单位: 字节
&数组名 - 数组名表示整个数组, 取出的是整个数组的地址
4.4 数组作为函数参数 (冒泡排序) 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 void bubble_sort (int arr[], int sz) { int sz = sizeof (arr)/sizeof (arr[0 ]); int i = 0 ; for (i=0 ; i<sz-1 ; i++) { int j = 0 ; for (j=0 ; j<sz-i-1 ; j++) { if (arr[j] > arr[j+1 ]) { int tmp = arr[j]; arr[j] = arr[j+1 ]; arr[j+1 ] = tmp; } } } } int main () { int arr[] = { 3 , 1 , 7 , 5 , 8 , 9 , 0 , 2 , 4 , 6 }; int sz = sizeof (arr) / sizeof (arr[0 ]); bubble_sort(arr, sz); for (i = 0 ; i < sz; i++) { printf ("%d " , arr[i]); } return 0 ; }
4.5 三子棋
logic.c 测试游戏的逻辑
game.h 关于游戏函数的声明, 符号声明, 包含的头文件
game.c 游戏相关函数实现
4.6 扫雷
logic.c 测试游戏的逻辑
game.h 关于游戏函数的声明, 符号声明, 包含的头文件
game.c 游戏相关函数实现
五、操作符 5.1 操作符分类
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
5.2 算数操作符
除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
% 操作符的两个操作数必须为整数。返回的是整除之后的余数。
5.3 移位操作符
对于移位运算符,不要移动负数位,这个是标准未定义的 num>>-1; //error
内存中存放的是二进制的补码 —–即操作的是补码
>> 右移操作符 (通常算数右移)
逻辑移位
左边用0填充,右边丢弃
算术移位
左边用原该值的符号位填充,右边丢弃
5.4 位操作符
& //按位与
两个数位值均为 1 , 得数位值为 1 , 其余均为 0
| //按位或
两个数位值均为 0 , 得数位值为 0 , 其余均为 1
^ //按位异或
两个数位值相同为 0 , 相异必须是整数。
注:他们的操作数必须是整数
注:他们的操作数必须是整数。
1 2 3 4 5 6 7 8 9 10 11 12 13 int main () { int a = 1 ; int b = 2 ; printf ("a = %d, b = %d" , a, b); a^ a; a = a ^ b; b = a ^ b; a = a ^ b; printf ("a = %d, b = %d" , a, b); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 int main () { int num = -1 ; int i = 0 ; int count = 0 ; while (num) { count++; num = num & (num - 1 ); } printf ("二进制中1的个数 = %d\n" , count); return 0 ; }
5.5 赋值操作符
复合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
5.6 单目操作符
操作符
作用
!
逻辑反操作
-
负值
+
正值
&
取地址
sizeof
操作数的类型长度(以字节为单位)
~
对一个数的二进制按位取反
–
前置、后置– (写在前面就先 - )
++
前置、后置++ (写在前面就先 + )
*
间接访问操作符(解引用操作符) — 用在指针
(类型)
强制类型转换
5.7 关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
5.8 逻辑操作符
&& 逻辑与
|| 逻辑或
1 2 3 4 5 6 7 8 9 int main () { int i = 0 , a = 0 , b = 2 , c = 3 , d = 4 ; i = a++ && ++b && d++; printf ("a = %d\n b = %d\n c = %d\nd = %d\n" , a, b, c, d); return 0 ; }
5.9 条件操作符
exp1 ? exp2 : exp3
5.10 逗号表达式
exp1, exp2, exp3, ...expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
5.11 下标引用、函数调用和结构成员
[] 下标引用操作符
() 函数调用操作符
. 结构体.成员名
-> 结构体指针->成员名
5.12 表达式求值 隐式类型转换 C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的 字符和短整型 操作数在使用之前被转换为普通整型,这种转换称为整型提升 。
说白了就是因为 char ( 1 字节) 跟 short (2 字节) 太小了, 达不到运算时所需要的 4 字节, 才会隐式转成整型
向精度更高的转换
整型提升的意义 :
表达式的整型运算要在 CPU 的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个 char 类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于 int 长度的整型值,都必须先转
换为 int 或 unsigned int,然后才能送入CPU去执行运算。
1 2 3 char a,b,c;... a = b + c;
如何进行整体提升?
整形提升是按照变量的数据类型的符号位来提升的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 char c1 = -1 ;变量c1的二进制位(补码)中只有8 个比特位: 1111111 因为 char 为有符号的 char 所以整形提升的时候,!!! 高位补充符号位,即为1 !!! 提升之后的结果是: 11111111111111111111111111111111 char c2 = 1 ;变量c2的二进制位(补码)中只有8 个比特位: 00000001 因为 char 为有符号的 char 所以整形提升的时候,!!! 高位补充符号位,即为0 !!! 提升之后的结果是: 00000000000000000000000000000001
例1
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 int main () { char a = 3 ; char b = 127 ; char c = a + b; printf ("%d\n" , c); return 0 ; }
例2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int main () { char a = 0xb6 ; short b = 0xb600 ; int c = 0xb6000000 ; if (a == 0xb6 ) printf ("a" ); if (b == 0xb600 ) printf ("b" ); if (c == 0xb6000000 ) printf ("c" ); return 0 ; }
例3
1 2 3 4 5 6 7 8 9 10 11 12 int main () { char c = 1 ; printf ("%u\n" , sizeof (c)); printf ("%u\n" , sizeof (+c)); printf ("%u\n" , sizeof (-c)); return 0 ; }
算数转换 如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类
型,否则操作就无法进行。下面的层次体系称为寻常算术转换 。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算
从下往上转, 下面精度低, 上面精度高, 从精度小的转换为精度大的
操作符的属性
操作符的优先级
操作符的结合性
是否控制求值顺序。
六、指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main () { int a = 3 ; int * indicator_a = &a; char b = 'a' ; char * indicator_b = &b; *indicator_a = 10 ; printf ("%d\n" , a); int num = 10 ; int * p; p = # *p = num; return 0 ; }
6.1 指针类型 char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main () { int n = 10 ; char * pc = (char *) &n; int * pi = &n; printf ("%p\n" , &n); printf ("%p\n" , pc); printf ("%p\n" , pc + 1 ); printf ("%p\n" , pi); printf ("%p\n" , pi + 1 ); return 0 ; }
输出:
1 2 3 4 5 0000005 C4BCFF8B40000005 C4BCFF8B40000005 C4BCFF8B50000005 C4BCFF8B40000005 C4BCFF8B8
1 2 3 4 5 6 7 8 9 10 11 int main () { int n = 0x11223344 ; char * pc = (char *)&n; int * pi = &n; *pc = 0 ; *pi = 0 ; return 0 ; }
6.2 字符指针 1 2 3 4 5 6 7 8 9 10 11 12 13 int main () { char ch = 'w' ; char * pc = &ch; *pc = 'w' ; const char * pstr = "hello bit." ; printf ("%s\n" , pstr); 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 24 25 26 int main () { char str1[] = "hello bit." ; char str2[] = "hello bit." ; const char * str3 = "hello bit." ; const char * str4 = "hello bit." ; if (str1 == str2) printf ("str1 and str2 are same\n" ); else printf ("str1 and str2 are not same\n" ); if (str3 == str4) printf ("str3 and str4 are same\n" ); else printf ("str3 and str4 are not same\n" ); return 0 ; }
6.3 野指针
指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int main () { int * p; *p = 20 ; return 0 ; } int main () { int arr[10 ] = { 0 }; int * p = arr; int i = 0 ; for (i = 0 ; i <= 11 ; i++) { *(p++) = i; } return 0 ; }
规避野指针:
指针初始化
小心指针越界
指针指向空间释放即使置NULL
避免返回局部变量的地址
指针使用之前检查有效性
6.4 指针运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (vp = &values[N_VALUES]; vp > &values[0 ];) { *--vp = 0 ; } for (vp = &values[N_VALUES - 1 ]; vp >= &values[0 ];vp--) { *vp = 0 ; }
6.5 指针与数组 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 int main () { int arr[10 ] = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,0 }; printf ("%p == %p" , arr, &arr[0 ]); int arr[10 ] = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,0 }; int * p = arr; return 0 ; } int main () { int arr[] = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,0 }; int * p = arr; int sz = sizeof (arr) / sizeof (arr[0 ]); for (int i = 0 ; i < sz; i++) { printf ("&arr[%d] = %p <====> p+%d = %p\n" , i, &arr[i], i, p + i); } return 0 ; } int main () { int arr[] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 }; int * p = arr; int sz = sizeof (arr) / sizeof (arr[0 ]); int i = 0 ; for (i = 0 ; i < sz; i++) { printf ("%d " , *(p + i)); } return 0 ; }
6.6 指针数组 1 2 3 int * arr3[5 ];char * arr4[5 ];
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 int main () { int a = 10 ; int b = 20 ; int c = 30 ; int * arr[3 ] = { &a, &b, &c }; for (int i = 0 ; i < 3 ; i++) { printf ("%d " , *(arr[i])); } int a1[5 ] = { 1 , 2 , 3 , 4 , 5 }; int b1[5 ] = {}; int c1[5 ] = {}; int * arr1[3 ] = { a1, b1, c1 }; for (int i = 0 ; i < 3 ; i++) { for (int j = 0 ; j < 5 ; j++) { printf ("%d " , *(arr1[i] + j)); printf ("%d " , arr[i][j]); } } return 0 ; }
6.7 二级指针 1 2 3 4 5 6 7 8 int b = 20 ;int * ppa = &b;int * *ppa = &ppa;**ppa = 30 ;
6.8 数组指针 1 2 3 4 5 6 7 int main () { int arr[10 ] = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,0 }; int (*p)[10 ] = &arr; 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 24 25 26 27 28 29 void print_arr1 (int arr[3 ][5 ], int row, int col) { for (int i = 0 ; i < row; i++) { for (int j = 0 ; j < col; j++) { printf ("%d " , arr[i][j]); } printf ("\n" ); } } void print_arr2 (int (*arr)[5 ], int row, int col) { for (int i = 0 ; i < row; i++) { for (int j = 0 ; j < col; j++) { printf ("%d " , arr[i][j]); } printf ("\n" ); } } int main () { int arr[3 ][5 ] = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 }; print_arr1(arr, 3 , 5 ); print_arr2(arr, 3 , 5 ); return 0 ; }
1 2 3 4 5 int arr[5 ]; int * parr1[10 ]; int (*parr2)[10 ]; int (*parr3[10 ])[5 ];
6.9 数组参数 & 指针参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void test (int arr[]) {} void test (int arr[10 ]) {} void test (int * arr) {} void test2 (int * arr[20 ]) {} void test2 (int ** arr) {} int main () { int arr[10 ] = { 0 }; int * arr2[20 ] = { 0 }; test(arr); test2(arr2); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void test (int arr[3 ][5 ]) {} void test (int arr[][]) {} void test (int arr[][5 ]) {} void test (int * arr) {} void test (int * arr[5 ]) {} void test (int (*arr)[5 ]) {} void test (int ** arr) {} int main () { int arr[3 ][5 ] = { 0 }; test(arr); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void print (int * p, int sz) { int i = 0 ; for (i = 0 ; i < sz; i++) { printf ("%d\n" , *(p + i)); } } int main () { int arr[10 ] = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 }; int * p = arr; int sz = sizeof (arr) / sizeof (arr[0 ]); print(p, sz); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 void test (int ** ptr) { printf ("num = %d\n" , **ptr); } int main () { int n = 10 ; int * p = &n; int ** pp = &p; test(pp); test(&p); return 0 ; }
6.10 函数 & 指针 函数指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void test (char *) {} int Add (int , int ) {} int main () { int (*pf)(int , int ) = &Add; int (*pf)(int , int ) = &Add; void (*pc)(char *) = &test; int ret = (*pf)(3 , 5 ); return 0 ; }
函数指针数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 int test (int , int ) {} int Add (int , int ) {} int main () { int (*pf[10 ])(int , int ) = { Add, test }; 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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int add (int a, int b) { return a + b; } int sub (int a, int b) { return a - b; } int mul (int a, int b) { return a * b; } int div (int a, int b) { return a / b; } int main () { int x, y; int input = 1 ; int ret = 0 ; int (*p[5 ])(int x, int y) = { 0 , add, sub, mul, div }; while (input) { printf ("*************************\n" ); printf (" 1:add 2:sub \n" ); printf (" 3:mul 4:div \n" ); printf ("*************************\n" ); printf ("请选择:" ); scanf ("%d" , &input); if ((input <= 4 && input >= 1 )) { printf ("输入操作数:" ); scanf ("%d %d" , &x, &y); ret = (*p[input])(x, y); } else { printf ("输入有误\n" ); } printf ("ret = %d\n" , ret); } return 0 ; }
指向函数指针数组的指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void test (const char * str) { printf ("%s\n" , str); } int main () { void (*pfun)(const char *) = test; void (*pfunArr[5 ])(const char * str); pfunArr[0 ] = test; void (*(*ppfunArr)[5 ])(const char *) = &pfunArr; return 0 ; }
回调函数
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,
而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int int_cmp (const void * p1, const void * p2) { return (*(int *)p1 - *(int *)p2); } int main () { int arr[] = { 1 , 3 , 5 , 7 , 9 , 2 , 4 , 6 , 8 , 0 }; int i = 0 ; qsort(arr, sizeof (arr) / sizeof (arr[0 ]), sizeof (int ), int_cmp); for (i = 0 ; i < sizeof (arr) / sizeof (arr[0 ]); i++) { printf ("%d " , arr[i]); } printf ("\n" ); 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 24 25 26 27 28 29 30 31 32 33 34 35 int int_cmp (const void * p1, const void * p2) { return (*(int *)p1 - *(int *)p2); } void _swap(void * p1, void * p2, int size) { int i = 0 ; for (i = 0 ; i < size; i++) { char tmp = *((char *)p1 + i); *((char *)p1 + i) = *((char *)p2 + i); *((char *)p2 + i) = tmp; } } void bubble (void * base, int count, int size, int (*cmp)(const void *, const void *)) { int i = 0 ; int j = 0 ; for (i = 0 ; i < count - 1 ; i++) { for (j = 0 ; j < count - i - 1 ; j++) { if (cmp((char *)base + j * size, (char *)base + (j + 1 ) * size) > 0 ) { _swap((char *)base + j * size, (char *)base + (j + 1 ) * size, size); } } } } int main () { int arr[] = { 1 , 3 , 5 , 7 , 9 , 2 , 4 , 6 , 8 , 0 }; int i = 0 ; bubble(arr, sizeof (arr) / sizeof (arr[0 ]), sizeof (int ), int_cmp); for (i = 0 ; i < sizeof (arr) / sizeof (arr[0 ]); i++) { printf ("%d " , arr[i]); } printf ("\n" ); return 0 ; }
6.11 一些笔试题 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 int main () { int a[] = { 1 ,2 ,3 ,4 }; printf ("%d\n" , sizeof (a)); printf ("%d\n" , sizeof (a + 0 )); printf ("%d\n" , sizeof (*a)); printf ("%d\n" , sizeof (a + 1 )); printf ("%d\n" , sizeof (a[1 ])); printf ("%d\n" , sizeof (&a)); printf ("%d\n" , sizeof (*&a)); printf ("%d\n" , sizeof (&a + 1 )); printf ("%d\n" , sizeof (&a[0 ])); printf ("%d\n" , sizeof (&a[0 ] + 1 )); char arr[] = { 'a' ,'b' ,'c' ,'d' ,'e' ,'f' }; printf ("%d\n" , sizeof (arr)); printf ("%d\n" , sizeof (arr + 0 )); printf ("%d\n" , sizeof (*arr)); printf ("%d\n" , sizeof (arr[1 ])); printf ("%d\n" , sizeof (&arr)); printf ("%d\n" , sizeof (&arr + 1 )); printf ("%d\n" , sizeof (&arr[0 ] + 1 )); printf ("%d\n" , strlen (arr)); printf ("%d\n" , strlen (arr + 0 )); printf ("%d\n" , strlen (*arr)); printf ("%d\n" , strlen (arr[1 ])); printf ("%d\n" , strlen (&arr)); printf ("%d\n" , strlen (&arr + 1 )); printf ("%d\n" , strlen (&arr[0 ] + 1 )); char arr[] = "abcdef" ; printf ("%d\n" , sizeof (arr)); printf ("%d\n" , sizeof (arr + 0 )); printf ("%d\n" , sizeof (*arr)); printf ("%d\n" , sizeof (arr[1 ])); printf ("%d\n" , sizeof (&arr)); printf ("%d\n" , sizeof (&arr + 1 )); printf ("%d\n" , sizeof (&arr[0 ] + 1 )); printf ("%d\n" , strlen (arr)); printf ("%d\n" , strlen (arr + 0 )); printf ("%d\n" , strlen (*arr)); printf ("%d\n" , strlen (arr[1 ])); printf ("%d\n" , strlen (&arr)); printf ("%d\n" , strlen (&arr + 1 )); printf ("%d\n" , strlen (&arr[0 ] + 1 )); char * p = "abcdef" ; printf ("%d\n" , sizeof (p)); printf ("%d\n" , sizeof (p + 1 )); printf ("%d\n" , sizeof (*p)); printf ("%d\n" , sizeof (p[0 ])); printf ("%d\n" , sizeof (&p)); printf ("%d\n" , sizeof (&p + 1 )); printf ("%d\n" , sizeof (&p[0 ] + 1 )); printf ("%d\n" , strlen (p)); printf ("%d\n" , strlen (p + 1 )); printf ("%d\n" , strlen (*p)); printf ("%d\n" , strlen (p[0 ])); printf ("%d\n" , strlen (&p)); printf ("%d\n" , strlen (&p + 1 )); printf ("%d\n" , strlen (&p[0 ] + 1 )); int a[3 ][4 ] = { 0 }; printf ("%d\n" , sizeof (a)); printf ("%d\n" , sizeof (a[0 ][0 ])); printf ("%d\n" , sizeof (a[0 ])); printf ("%d\n" , sizeof (a[0 ] + 1 )); printf ("%d\n" , sizeof (*(a[0 ] + 1 ))); printf ("%d\n" , sizeof (a + 1 )); printf ("%d\n" , sizeof (*(a + 1 ))); printf ("%d\n" , sizeof (&a[0 ] + 1 )); printf ("%d\n" , sizeof (*(&a[0 ] + 1 ))); printf ("%d\n" , sizeof (*a)); printf ("%d\n" , sizeof (a[3 ])); }
1 2 3 4 5 6 7 8 int main () { int a[5 ] = { 1 , 2 , 3 , 4 , 5 }; int * ptr = (int *)(&a + 1 ); printf ("%d,%d" , *(a + 1 ), *(ptr - 1 )); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct Test { int Num; char * pcName; short sDate; char cha[2 ]; short sBa[4 ]; }*p; int main () { printf ("%p\n" , p + 0x1 ); printf ("%p\n" , (unsigned long )p + 0x1 ); printf ("%p\n" , (unsigned int *)p + 0x1 ); return 0 ; }
1 2 3 4 5 6 7 8 9 int main () { int a[4 ] = { 1 , 2 , 3 , 4 }; int * ptr1 = (int *)(&a + 1 ); int * ptr2 = (int *)((int )a + 1 ); printf ("%x,%x" , ptr1[-1 ], *ptr2); return 0 ; }
1 2 3 4 5 6 7 8 9 int main () { int a[3 ][2 ] = { (0 , 1 ), (2 , 3 ), (4 , 5 ) }; int * p; p = a[0 ]; printf ("%d" , p[0 ]); return 0 ; }
1 2 3 4 5 6 7 8 9 int main () { int a[5 ][5 ]; int (*p)[5 ]; p = a; printf ("%p,%d\n" , &p[4 ][2 ] - &a[4 ][2 ], &p[4 ][2 ] - &a[4 ][2 ]); return 0 ; }
1 2 3 4 5 6 7 8 9 int main () { int aa[2 ][5 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; int * ptr1 = (int *)(&aa + 1 ); int * ptr2 = (int *)(*(aa + 1 )); printf ("%d,%d" , *(ptr1 - 1 ), *(ptr2 - 1 )); return 0 ; }
1 2 3 4 5 6 7 8 9 int main () { char *a[] = {"work" ,"at" ,"alibaba" }; char **pa = a; pa++; printf ("%s\n" , *pa); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 int main () { char *c[] = {"ENTER" ,"NEW" ,"POINT" ,"FIRST" }; char **cp[] = {c+3 ,c+2 ,c+1 ,c}; char ***cpp = cp; printf ("%s\n" , **++cpp); printf ("%s\n" , *--*++cpp+3 ); printf ("%s\n" , *cpp[-2 ]+3 ); printf ("%s\n" , cpp[-1 ][-1 ]+1 ); return 0 ; }
七、结构体 7.1 结构体声明
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct Student { char name[100 ]; int age; double average; }student_number; struct Book { struct Student borrow ; int year; } book_name;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct Point { int x; int y; }p1; struct Point p2 ; struct Point p3 = { x, y };struct Stu { char name[15 ]; int age; }; struct Stu s = { "zhangsan" , 20 };struct Node { int data; struct Point p ; struct Node * next ; }n1 = { 10 , {4 ,5 }, NULL }; struct Node n2 = { 20 , {5 , 6 }, NULL };
7.2 结构体成员访问 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct Stu { char name[20 ]; int age; }; void print (struct Stu* ps) { printf ("name = %s age = %d\n" , (*ps).name, (*ps).age); printf ("name = %s age = %d\n" , ps->name, ps->age); } int main () { struct Stu s = { "zhangsan" , 20 }; print(&s); return 0 ; }
7.3 结构体传参
首选传址
函数传参的时候,参数是需要压栈的。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
说白了就是,
如果传值的话, 调用函数的时候就要新建一个跟结构体大小相等的变量, 开辟一个跟结构体大小相等的空间, 增大没必要的性能损耗
而且形参的改变无法改变实参, 有些功能函数传值没意义, 也对原结构体无法进行修改
如果传址就好办了, 直接对地址进行操作, 无需开辟另一个空间, 而且也可以直接对实参进行操作, 一句多得
!!!!!不要恐惧指针!!!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct S { int data[1000 ]; int num; }; struct S s = { {1 ,2 ,3 ,4 }, 1000 };void print1 (struct S s) { printf ("%d\n" , s.num); } void print2 (struct S* ps) { printf ("%d\n" , ps->num); } int main () { print1(s); print2(&s); return 0 ; }
八、数据的存储 8.1 数据类型 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 char unsigned char signed char short unsigned short [int ] signed short [int ] int unsigned int signed int long unsigned long [int ] signed long [int ] float double 数组类型 结构体类型 struct 枚举类型 enum 联合类型 union //指针类型 int * pi ;char * pc;float * pf;void * pv;
8.2 整型在内存中的存储 计算机中的整数有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位 和数值位 两部分,符号位都是用0表示“正”,用1表示“负”
正数的原、反、补码都相同。
对于整形来说:数据存放内存中其实存放的是补码。
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;
同时,加法和减法也可以统一处理(CPU****只有加法器 )此外,补码与原码相互转换,其运算过程
是相同的,不需要额外的硬件电路。
大端小端
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。
在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8 bit。但是在C语言中除了 8 bit 的 char 之外,还有 16 bit 的 short 型,32 bit 的 long 型(要看具体的编译器),另外,对于位数大于 8 位的处理器,例如 16 位或者 32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个 16 bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的 ARM,DSP 都为小端模式。有些 ARM 处理器还可以由硬件来选择是大端模式还是小端模式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int check_sys () { int i = 1 ; return (*(char *)&i); } int main () { int ret = check_sys(); if (ret == 1 ) { printf ("小端\n" ); } else { printf ("大端\n" ); } return 0 ; } int check_sys () { union { int i; char c; }un; un.i = 1 ; return un.c; }
1 2 3 4 5 6 7 8 9 int main () { char a = -1 ; signed char b = -1 ; unsigned char c = -1 ; printf ("a = %d, b = %d, c = %d" , a, b, c); return 0 ; }
1 2 3 4 5 6 7 int main () { char a = -128 ; printf ("%u\n" , a); return 0 ; }
1 2 3 4 5 6 7 int main () { char a = 128 ; printf ("%u\n" , a); return 0 ; }
1 2 3 4 5 6 7 8 9 int main () { int i = -20 ; unsigned int j = 10 ; printf ("%d\n" , i + j); return 0 ; }
1 2 3 4 5 6 7 8 int main () { unsigned int i; for (i = 9 ; i >= 0 ; i--) { printf ("%u\n" , i); } 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 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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 4294933570 4294933569 4294933568 4294933567 4294933566 4294933565 4294933564 4294933563 4294933562 4294933561 4294933560 4294933559 4294933558 4294933557 4294933556 4294933555 4294933554 4294933553 4294933552 4294933551 4294933550 4294933549 4294933548 4294933547 4294933546 4294933545 4294933544 4294933543 4294933542 4294933541 4294933540 4294933539 4294933538 4294933537 4294933536 4294933535 4294933534 4294933533 4294933532 4294933531 4294933530 4294933529 4294933528 4294933527 4294933526 4294933525 4294933524 4294933523 4294933522 4294933521 4294933520 4294933519 4294933518 4294933517 4294933516 4294933515 4294933514 4294933513 4294933512 4294933511 4294933510 4294933509 4294933508 4294933507 4294933506 4294933505 4294933504 4294933503 4294933502 4294933501 4294933500 4294933499 4294933498 4294933497 4294933496 4294933495 4294933494 4294933493 4294933492 4294933491 4294933490 4294933489 4294933488 4294933487 4294933486 4294933485 4294933484 4294933483 4294933482 4294933481 4294933480 4294933479 4294933478 4294933477 4294933476 4294933475 4294933474 4294933473 4294933472 4294933471 4294933470 4294933469 4294933468 4294933467 4294933466 4294933465 4294933464 4294933463 4294933462 4294933461 4294933460 4294933459 4294933458 4294933457 4294933456 4294933455 4294933454 4294933453 4294933452 4294933451 4294933450 4294933449 4294933448 4294933447 4294933446 4294933445 4294933444 4294933443 4294933442 4294933441 4294933440 4294933439 4294933438 4294933437 4294933436 4294933435 4294933434 4294933433 4294933432 4294933431 4294933430 4294933429 4294933428 4294933427 4294933426 4294933425 4294933424 4294933423 4294933422 4294933421 4294933420 ...
1 2 3 4 5 6 7 8 9 10 11 int main () { char a[1000 ]; int i; for (i = 0 ; i < 1000 ; i++) { a[i] = -1 - i; } printf ("%d" , strlen (a)); return 0 ; }
1 2 3 4 5 6 7 8 9 unsigned char i = 0 ;int main () { for (i = 0 ;i <= 255 ;i++) { printf ("hello world\n" ); } return 0 ; }
8.3 浮点型在内存中的存储 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int main () { int n = 9 ; float * pFloat = (float *)&n; printf ("n的值为:%d\n" , n); printf ("*pFloat的值为:%f\n" , *pFloat); *pFloat = 9.0 ; printf ("num的值为:%d\n" , n); printf ("*pFloat的值为:%f\n" , *pFloat); return 0 ; }
任意一个二进制浮点数 V 可以表示成下面的形式:
十进制的 5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。
那么,按照上面 V 的格式,可以得出 s=0,M=1.01,E=2。
十进制的 -5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么 s=1,M=1.01,E=2。
对于 32 位的浮点数,最高的 1 位是符号位 s,接着的 8 位是指数 E,剩下的 23 位为有效数字 M。
对于 64 位的浮点数,最高的 1 位是符号位 S,接着的 11 位是指数 E,剩下的 52 位为有效数字 M。
在计算机内部保存 M 时,默认这个数的第一位总是 1,因此可以被舍去,只保存后面的xxxxxx部分。
比如保存 1.01 的时候,只保存 01,等到读取的时候,再把第一位的 1 加上去。这样做的目的,是节省 1 位有效数字。
以 32 位浮点数为例,留给 M 只有 23 位,将第一位的 1 舍去以后,等于可以保存 24 位有效数字。
至于指数 E,情况就比较复杂。
首先,E 为一个无符号整数(unsigned int)
这意味着,如果 E 为 8 位,它的取值范围为 0~255;如果 E 为 11 位,它的取值范围为 0~2047。
但是,我们知道,科学计数法中的E是可以出现负数的,
所以 IEEE 754 规定,存入内存时 E 的真实值必须再加上一个中间数,
对于 8 位的 E,这个中间数是 127;对于 11 位的 E,这个中间数是 1023。
比如,2^10 的 E 是 10,所以保存成 32 位浮点数时,必须保存成 10+127=137,即 10001001。
然后,指数E从内存中取出还可以再分成三种情况:
这时,浮点数就采用下面的规则表示,
即指数 E 的计算值减去 127(或1023),得到真实值,再将有效数字 M 前加上第一位的 1。
比如:
0.5(1/2)的二进制形式为 0.1,由于规定正数部分必须为 1,即将小数点右移 1 位,则为 1.0*2^(-1),其阶码为 -1+127=126,
表示为 01111110,而尾数 1.0 去掉整数部分为 0,补齐 0 到 23 位 00000000000000000000000,
则其二进制表示形式为:
0 01111110 00000000000000000000000
这时,浮点数的指数E等于 1-127(或者1-1023)即为真实值,
有效数字 M 不再加上第一位的1,而是还原为 0.xxxxxx 的小数。这样做是为了表示 ±0 ,以及接近于 0 的很小的数字。
这时,如果有效数字 M 全为 0,表示 ±无穷大(正负取决于符号位 s);
下面,让我们回到一开始的问题:为什么 0x00000009 还原成浮点数,就成了 0.000000 ?
首先,将 0x00000009 拆分,得到第一位符号位 s=0,后面 8 位的指数 E=00000000 ,
最后 23 位的有效数字 M=000 0000 0000 0000 0000 1001。
9 -> 0000 0000 0000 0000 0000 0000 0000 1001
由于指数 E 全为 0,所以符合上一节的第二种情况。因此,浮点数 V 就写成:
V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146)
显然,V 是一个很小的接近于 0 的正数,所以用十进制小数表示就是 0.000000。
再看例题的第二部分。
请问浮点数 9.0,如何用二进制表示?还原成十进制又是多少?
首先,浮点数 9.0 等于二进制的 1001.0,即 1.001×2^3。
9.0 -> 1001.0 ->(-1)^01.0012^3 -> s=0, M=1.001,E=3+127=130
那么,第一位的符号位 s=0,有效数字 M 等于 001 后面再加 20 个 0,凑满 23 位,指数 E 等于 3+127=130,即 10000010。
所以,写成二进制形式,应该是 s+E+M,即
0 10000010 001 0000 0000 0000 0000 0000
这个 32 位的二进制数,还原成十进制,正是 1091567616 。