C语言学习进程(翁恺)

 1.0——C的基本结构及语句

1.1——基本数据类型

1.2——变量定义

1.3——表达式

1.4——输入输出

1.5——格式字符表

2.0——选择结构(if…else…)

2.1——关系表达式

2.2——逻辑表达式

2.3——if...else...选择结构语句

2.4——注释(comment)

3.0——循环结构

3.1——循环(loop)

3.2——循环practice1

4.0——打破循环&结束循环

4.1——break & continue

4.2——跳出嵌套循环

4.2.1——goto

4.2.2——接力break

5.0——阶段练习

1.2—— 多路分支 switch-case

 

 

 

 

1.0——C的框架

# include <stdio.h>
int main ()
{
return 0;
}

 

例:Hello World
 #include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}

 

1.1——基本数据类型

一、整型数据(整数)

  • 两个整数的运算只能是整数
  • 整数计算没有误差
  • 一般用int

二、浮点型数据(浮点数)

  • 浮点数 double 输入%lf 输出%f
  • 浮点数存在误差
  • 有float,double ,long double
  • 可以用指数形式表示

三、字符型数据(ASCII码)

  • 用char表示
  • 可以解释为整数,也可以解释为字符
  • 内部表示按照字符的编码(ASCII码)

 

1.2——变量定义

变量定义一般形式

类型 标识符列表

标识符:英文字母,数字,下划线

变量初始化 

定义常量 const int AMOUNT = 100;

变量定义实例
int _5 = 0;
int _abc = 0;
int sum = 0;
double price_per_student ;
const int AMOUNT = 100; //常量一般大写
错误
int 1a = 0; //error

 

 1.3——表达式

  • 运算符 + - * / % = ……
  • 算子:参与运算的值,例如,a = a + 5 + 1;算子为a,5,1
  • 运算符存在优先级
  • 复合赋值x *= y+1相当于x = x*(y+1)
  • 自增减,++,--

 

自增减

自增减实例
 # include <stdio.h>
int main ()
{
int x = 10;
int plus = x,plus2 = x;
int minus = x,minus2 = x;
printf ("plus++=%d,minus--=%d,++plus2=%d,--minus=%d\n",plus++,minus--,++plus2,--minus2);
x = 10;
plus = x,plus2 = x,minus=x,minus2=x;
plus ++;
minus --;
++plus2 ;
--minus2 ;
printf ("plus=%d,minus=%d,plus2=%d,minus=%d\n",plus,minus,plus2,minus2);
return 0;
}

 1.4——输入输出

一、输入函数

scanf()输入任何类型的数据

getchar()获取单个字符

输入函数实例
 # include <stdio.h>
int main ()
{
int i;
scanf ("%d",&i);
char a;
a = getchar(); //函数括号内为空
return 0;
}

二、输出函数

printf ()做任意输出

putchar()输出单个字符

输出函数实例
 # include <stdio.h>
int main ()
{
double grade = 4.0;
printf("my grade is %f",grade);
char a = 'A';
putchar(a);
putchar('A');
putchar(65); //三者等价
return 0;
}

三、输入输出格式说明

输入输出数据的格式需要用格式字符加以区分

1.5——格式字符表

if

格式字符 说明
%d 以十进制形式输出整型数据
%o 以无符号八进制形式输出整型数据
%X,%x 以无符号十六进制形式输出整型数据(%X时字母大写)
%c 以字符型式输出,只输出一个字符
%s 输出字符串
%f 以小数形式输出浮点数(默认6位小数)

 


选择结构(if…else…)

 

2.1——关系表达式

  • 有六个关系运算符

关系运算结果成立则为1,否则为0

关系表达式就是含有关系运算符的表达式

关系运算符
 关系运算符
== 相等
!= 不相等
> 大于
>= 大于或等于
< 小于
<= 小于或等于
注意其中有两个字符的运算符:==、>=和<=的两个字符必须紧紧连在一起,中间不能插入空格。
关系运算存在优先级:判断是否相等的 == 和 != 的优先级更低
运算关系:从左到右
关系运算结果成立则为1,否则为0
example:printf(" %d", 7 >= 3+4);

2.2——逻辑表达式

含有逻辑运算符的表达式称为逻辑表达式

C语言中有三个逻辑运算符,即&&、||、!

注意&&、||为双目运算符,为单目运算符

逻辑表达式的结果为0或1

逻辑运算

自左向右,如果左边的结果可以决定结果了, 就不会做右边的计算 

结果是0 或 1

运算符 例
! !a
&& a && b
|| a || b

 

2.3——if...else...选择结构语句

if(表达式)  //若表达式结果非0,执行语句一

  语句一;

else

  语句二;

if...else...实例
 if ( ){
}else {
}
if (score < 60 )
printf(" not pass);
else
printf(" pass "); //最好加上{}

 2.4——注释(comment)

// 初始化
int price = 0;
/* 42 */

if 再探

if...else...的嵌套使用

用于区分多种情况

if...else...的嵌套使用
if (){
}else if ()
{
}else if ()
{
}else {
}

 


 3.0——循环结构

3.1——循环(loop)

循环体内要有打破循环的条件

while ( ){
;
;
}
判断,再执行
do
{
<循环体语句>
}while( );
for 念做 对于
( ; ; )for 中的可以省略
for ( <初始条件> ; <循环继续条件> ; <每轮动作> ){
}

 

3.2——循环practice1

等差数列求和
# include <stdio.h>
int main ()
{
printf("请输入首项 公差 项数(整数)\n ") ;
int a,d,n;
int sum = 0;
// a=1,d=1,n=100; //测试
scanf("%d %d %d",&a,&d,&n);
int i = n;
sum = a;
for (n = n-1;n>0;n--)
{
int t = n;
int j = a;
for (t = t;t>0;t--)
{
j += d;
} //求第n项的值
sum += j;
}
printf("前%d项和为%d",i,sum);
return 0;
}
估算自然常数e
 # include <stdio.h>
int fact (int i);
int main ()
{
//利用e^x的麦克劳林展开估算自然常数e
//e= 1+ 1/1! + 1/2! +1/3! +……
double e = 1.0;
int n;
printf("请输入精度\n");
scanf("%d",&n);
int i = 1;
for (i=1;i<=n;i++){
e += 1.0/fact(i);
}
printf("e=%f\n",e);
return 0;
}
int fact (int i)
{
int fact = 1;
if(i==0){
i=1;
}
for(fact=1;i>0;i--){
fact *= i;
}
return fact;
}

 

 

   

优先级

单目 > 双目

关系 > 逻辑 > 条件 > 赋值

 

条件运算方向:自右向左

逗号运算for (i=0 ,j=10 ;i<j ;i++,j-- )

 

 

多路分支 switch-case

switch ( type ){
case 1:
printf("hello\n");
break;
case 2:
break;
default:
break;
}

  


4.0——打破循环&结束循环

4.1——break & continue

break可跳出所在那层循环,而continue则结束这一次循环

// 判断素数
int x = 0;
int i = 2;
int isPrime = 1;
scanf("%d", &x );
for ( i=2 ; i<x ; i++ )
{
if (x % i == 0 ){
isPrime = 0;
break;
}
}
if (isPrime == 1){
printf ("是素数");
}else{
printf("不是素数");
}
return 0;

 4.2——跳出嵌套循环

4.2.1——goto

// 如何用1角、2角和5角的硬币凑出10元以下的金额
int x = 0;
int one , two , five = 0;
scanf ("%d",&x);
for ( one = 0 ; one <x*10 ; one++){
for ( two = 0 ; two <x*10/2 ; two++){
for ( five = 0 ; five <x*10/5 ; five++){
if (1*one + 2*two + 5*five == x*10 ){
printf("%d个一角和%d个两角和%d个五角可以合成%d元\n",one ,two , five , x);
goto out;
}
}
}
}
out :
return 0;

4.2.2——接力break

// 如何用1角、2角和5角的硬币凑出10元以下的金额
int x = 0;
int one , two , five = 0;
int exit = 0;
scanf ("%d",&x);
for ( one = 0 ; one <x*10 ; one++){
for ( two = 0 ; two <x*10/2 ; two++){
for ( five = 0 ; five <x*10/5 ; five++){
if (1*one + 2*two + 5*five == x*10 ){
printf("%d个一角和%d个两角和%d个五角可以合成%d元\n",one ,two , five , x);
exit = 1;
break;
}
}
if (exit == 1) break;
}
if (exit == 1) break;
}
return 0;

 


5.0——阶段练习

输入三个整数 ,输出其中 最小数
 # include <stdio.h>
int main ()
{
// 输入三个整数 ,输出其中 最小数
int a,b,c ;
scanf ("%d %d %d",&a,&b,&c);
int min = 0;
if (a <= b){
min = a;
} else {
min = b;
}
if (min <= c){
min = min;
}else {
min = c;
}
printf ("%d\n",min);
return 0;
}
素数判断
  int x = 0;
int i = 2;
int isPrime = 1;
scanf("%d", &x );
for ( i=2 ; i<x ; i++ )
{
if (x % i == 0 ){
isPrime = 0;
break;
}
}
if (isPrime == 1){
printf ("是素数");
}else{
printf("不是素数");
}
return 0;
整数的正分解
 // 整数的正分解
int x = 0;
int mask = 1;
int t = 0;
scanf ("%d", &x);
t = x;
while ( t>9 ){
t /= 10;
mask *= 10;
}
do{
printf ("%d", x/mask);
if ( mask>9 ){
printf(" ");
}
x %= mask;
mask /= 10;
}while( mask>0 );
return 0;
给定不超过6的正整数A,考虑从A开始的连续4个数字。请输出所有由它们组成的无重复数字的3位数
 // 给定不超过6的正整数A,考虑从A开始的连续4个数字。请输出所有由它们组成的无重复数字的3位数
int x = 0;
int i,j,k;
int cnt = 0;
scanf("%d",&x);
for ( i=x ; i<=x+3 ; i++ ){
for ( j=x ; j<=x+3 ; j++){
for ( k=x ; k<=x+3 ; k++){
if (i!=j && j!=k && i!=k){
printf("%d%d%d",i,j,k);
cnt++;
if (cnt == 6){
printf("\n");
cnt = 0;
}else{
printf(" ");
}
}
}
}
}
return 0;
水仙花数
// 水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身(例如:1^3 + 5^3+ 3^3 = 153),
int n = 3;
scanf ("%d",&n);
int mask = 1;
int t = n;
while(t>1){
t--;
mask *= 10;
}
// printf("%d",mask);
int i = mask;
for (i ;i<mask*10;i++){
// 先令t=i,d=mask
// 再分解t,求幂
// 求和
t=i;
int d=mask;
int sum = 0;
while(t>0){
int p = t/d;
int j = 1;
int k = p;
while (j<n){
k *= p;
j++;
}
sum += k;
t = t%d;
d /=10;
}
if (sum == i){
printf ("%d\n",i);
}
}
return 0;
九九乘法表
  int i,j = 1;
// 记每行为i*j,第一行j=1,第二行j=2
// 而每一行i递增到j
for (j=1;j<10;j++){
for (i=1;i<=j ;i++){
printf ("%d*%d=%d",i,j,i*j);
if (i*j<10){
printf (" ");
}else {
printf (" ");
}
}
printf("\n");
}
return 0;
统计素数并求和
 // 遍历m~n的数
// 判断素数,cnt ++
// printf
// sum
int m,n = 0;
scanf ("%d %d",&m,&n);
int i = 1;
int cnt = 0;
int sum = 0;
// m = 10;n = 31;
if (m ==1){
m = 2;
} //考虑 m=1 的特殊情况
for ( i=m ; i<=n ; i++ ){
int j = 1;
int isPrime = 1;
for ( j=2 ; j<i ; j++){
if ( i%j == 0){
isPrime = 0;
break;
}
}
if ( isPrime == 1){
cnt ++;
sum += i;
// printf("%d\n",i);
}
}
printf ("%d %d",cnt,sum);
return 0;
猜数游戏(mine)
 // 统计cnt,scanf n
// for 循环 n 次,猜中就break
// 判断正负,if
int x=12,n=2;
scanf ("%d %d",&x,&n);
int cnt = 0;
int a = 0;
do{
scanf ("%d",&a);
cnt ++;
if ( a<0 ) {
cnt = n+1;
break;
}
if ( a == x ){
break;
}else if ( a>x){
printf ("Too big\n");
}else {
printf ("Too small\n");
}
}while ( a!=x && cnt <n );
if ( cnt == 1){
printf ("Bingo!\n");
}else if ( cnt<=3 ){
printf ("Lucky You!\n");
}else if ( cnt<=n ){
printf ("Good Guess!");
}else {
printf ("Game Over");
}
// printf ("%d\n",cnt);
输入三个整数 ,输出它们构成的三角形中最大角的余弦值
 # include <stdio.h>
int imax (int a,int b ,int c);
int main ()
{
// 输入三个整数 ,输出它们构成的三角形中最大角的余弦值
int a,b,c ;
scanf ("%d %d %d",&a,&b,&c);
// a=1,b=2,c=2;
double cos = 0;
if ( a+b>c && a+c>b && b+c>a){
int max;
max = imax(a,b,c);
cos = (b*b+a*a+c*c-max*max*2)/(2.0*a*b*c/max);
printf ("%f\n",cos);
} else {
printf ("error,不能构成三角形");
}
return 0;
}
int imax (int a,int b ,int c)
{
int max = a;
if (b>max){
max = b;
}
if (c > max){
max = c;
}
return max;
}

 

 

 

 


数据类型

C语言的类型
整数; char ;short ;int ; long ;long long
逻辑: bool
浮点数;float ; double ; long double
指针
//个人认为还有 字符
类型名称:int ,long ,double
输入输出时的格式化:%d %ld %lf
表达数的范围:char < short < int < float < double
内存中占据字节的大小:1各字节到16个字节
内存中的表达形式:二进制数(补码)、编码

 

 sizeof

是判断数据类型或者表达式长度的运算符

例:int 为4个字节

 sizeof(int) = 4

 

 整数的内部表达

补码的认识

 

如果不以补码的形式,需加上 unsigned

例:unsigned char c = 255;

 

整数越界

# include <stdio.h>
int main ()
{
char c = 127;
c = c+1;
printf("c=%d",c);
return 0;
}
输出结果:c=-128

整数的输入输出

整数的输入输出
%d : int
%u : unsigned
%ld : long long
%lu : unsigned long long

 

8进制和16进制

  •  一个以0开始的数字字面量是8进制
  • 一个以0x(0X)开始的数字字面量是16进制
  • %o用于8进制,%x用于16进制
代码试验
  char c = 012;
int i = 0x12;
printf ("c=%d,i=%d",c,i);
return 0;
{
c=10,i=18 //输出结果
}
char c = 012;
int i = 0x1a;
printf ("c=%d,i=%d",c,i);
return 0;
{
c=10,i=26 //输出结果
}
char c = 012;
int i = 0x12;
printf ("c=%o,i=%x",c,i);
return 0;
{
c=12,i=12 //输出结果
}
char c = 012;
int i = 0x1a;
printf ("c=%o,i=%x\n",c,i);
i = 0X1a;
printf ("c=%o,i=%X",c,i);
return 0;
{
c=12,i=1a
c=12,i=1A //输出结果
}

 

 

浮点类型

  • float 有效数字 7
  • double 有效数字 15

浮点的输入与输出

类型 scanf printf
float %f %f,%e(E)
double %lf %f,%e(E)
浮点的输入与输出(例子)
 double ff = 1234.56789;
printf ("%f\n%e\n%E\n",ff,ff,ff);
ff = 1e-10; // 中间不能有空格
printf ("%f\n%e\n%E\n",ff,ff,ff);
printf ("%.16f\n",ff);
return 0;
{
1234.567890
1.234568e+003
1.234568E+003
0.000000
1.000000e-010
1.000000E-010
0.0000000001000000 //输出
}

输出精度

  • 在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做4舍5入
  • printf ("%.3f\n",-0.0049);
  • printf("%.30f\n",-0.0049);
  • printf("%.3f\n",-0.00049);

  

输出精度(实例)
printf ("%.3f\n",-0.0049);
printf("%.30f\n",-0.0049);
printf("%.3f\n",-0.00049);
printf ("%.3f\n",-0.0045); //此处我还未理解为啥结果不是-0.005
return 0;
{
-0.005
-0.004899999999999999800000000000
-0.000
-0.004 // 输出
}

 浮点运算的精度

float a,b,c ;
a = 1.345f;
b = 1.123f;
c = a + b ;
if (c == 2.468)
printf ("相等\n");
else
printf ("不相等! c=%.10f,或%f\n",c,c);
{
不相等! c=2.4679999352,或2.468000 //输出
}
  • 带小数点的字面量是double而非float
  • float需要用f或F后缀来表明身份
  • f1 == f2可能失败
  • fabs(f1-f2) < 1e-12 //判断浮点数相等的方法,fabs表示取绝对值

 

 字符(character)

  • char是一种整数,也是一种特殊的类型:字符。这是因为:
  • 用单引号表示的字符字面量:'a' , '1'
  • ''也是一个字符  //中间无空格
  • printf和scanf里用%c来输入输出字符
char x = 49;
char c = '1';
int i = 49;
scanf ("%c",&c);
scanf ("%d",&i);
x = i;
printf ("%c\n",x);

'1'的ASCII编码是49,所以c==49时,它代表'1'

 

 字符计算

字符计算实例
  char c = 'A';
c++;
printf ("%c\n",c) ;
int i = 'Z'-'A';
printf ("%d\n",i);
{
B
25 //输出
}
  • 一个字符加一个数字得到ASCII码表中那个数之后的字符
  • 两个字符的减,得到它们在表中的距离

大小写转换

  • 字母在ASCII表中是顺序排列的
  • 大写字母和小写字母是分开排列的,并不在一起
  • 'a'-'A'可以得到两段之间的距离,于是
  • 'X'+'a'-'A'可以把一个大写字母变成小写字母,而
  • 'x'+'A'-'a'可以把一个小写字母变成大写字母

 

 逃逸字符

  • 用来表达无法印出来的控制字符或特殊字符
    ,它由一个反斜杠“\”开头,后面跟上另一
    个字符,这两个字符合起来,组成了一个字符

printf ("请分别输入身高的英尺和英寸,如输入\"5 7\"表示5英尺7英寸");

字符 意义 字符 意义
\b 回退一格 \" 双引号
\t 到下一个表格位 \' 单引号
\n 换行 \\ 反斜杠本身
\r 回车    

 

自动类型转换

  • 当运算符的两边出现不一致的类型时,会自动转换成较大的类型
  • 大的意思是能表达的述的范围更大  //例如 整数会转换为浮点,int会转换为double
  • char -> short -> int -> long -> long long
  • int -> float -> double

 

  • 对于printf,任何小于int的类型会被转换成int;float会被转换成double
  • 但是scanf不会,要输入short,需要%hd

 

强制类型转换

  • 要把一个量强制转换成另一个类型(通常是较小的类型),需要:
  • (类型)值
  • 比如:
    • (int)10.2
    • (short)32
  • 注意这时候的安全性,小的变量不总能表达大的量
    • (short)32768

只是从那个变量计算出了一个新的类型的值,它并不改变那个变量,无论是值还是类型都不改变

  • 强制类型转换的优先级高于四则运算
强制类型转换实例
  double a = 1.0;
double b = 2.0;
int i = (int)a/b;
printf ("%d\n",i);
{
0 //输出
}

 

bool

  • #include<stdbool.h>
  • 之后就可以使用bool和true、false
bool实例
 # include <stdio.h>
# include <stdbool.h>
int main ()
{
bool t = true ;
bool b = true ;
t = false;
printf ("%d %d",b,t);
return 0;
}
{
1 0 // 输出
}

 

逻辑运算

  • 逻辑运算的结果只有0或1
  • 逻辑量是关系运算或逻辑运算的结果
运算符 描述 示例 结果
! 逻辑非 !a 如果a是true结果就是false(0),如果a是false结果就是true(1)
&& 逻辑与 a && b 如果a和b都是true,结果就是true;否则就是false
|| 逻辑或 a || b 如果a和b有一个是true,结果为true;两个都是false,结果为false

例 : 表达数学中 4<x<6

x>4 && x<6

问题:如何理解!age<20

单目运算符的优先级 高于 双目运算符,所以先对!age运算结果是0或1,再判断<20,所以必然成立,故结果为1(true)

 

优先级

  • ! > && > ||
  • 例题:理解 !done && (count >MAX)
优先级 运算符 结合性
1 () 从左到右
2 ! 、+ 、- 、++、 -- 从右到左(单目的+和-特殊)
3 * 、/ 、% 从左到右
4 +、 - 从左到右
5 <、<=、 >、 >=   从左到右
6 ==、 != 从左到右
7 && 从左到右
8 || 从左到右
9 = 、+=、 -=、 *= 、/= 、%= 从右到左

短路

不要把赋值,包括复合赋值组合进表达式!

  • 逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算
    • a==6 && b==1  
    • a==6 && b+=1  
  • 对于&&,左边是false时就不做右边了
  • 对于||,左边是true时就不做右边了

 

条件运算符

  • count = (count>20)?count - 10:count+10;
  • 条件、条件满足时的值和条件不满足时的值
  • 条件运算符的优先级高于赋值运算符,但是低于其它运算符
  • 条件运算符是自右向左结合的
  • x ? y : z;

逗号运算

  • 逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。
  • 逗号的优先级是所有的运算符中最低的,所以它两边的表达式会先计算
  • 逗号的组合关系是自左向右,所以左边的表达式会先计算,而右边的表达式的值就留下来作为逗号运算的结果
  • for (i=0,j =10 ; i<j ; i++,j--)

 


函数

  • “代码复制”是程序质量不良的表现

调用函数

  • 函数名(参数值);
  • ()起到了表示函数调用的重要作用
  • 即使没有参数也需要()
  • 如果有参数,则需要给出正确的数量和顺序
  • 这些值会被按照顺序依次用来初始化函数中的参数

从函数中返回值

  • return停止函数的执行,并送回一个值
  • return;
  • return 表达式; 
  • 一个函数里可以出现多个return语句

返回值

  • 可以赋值给变量
  • 可以再传递给函数
  • 甚至可以丢弃
  • 有的时候要的是副作用

没有返回值的函数

  • void函数名(参数表)
  • 不能使用带值的return
  • 可以没有return
  • 调用的时候不能做返回值的赋值
  • 如果函数有返回值,则必须使用带值的return

 


数组

定义数组

  • <类型>变量名称[元素数量]
    • int grades[100];
    • double weight[20];
  • 元素数量必须是整数
  • C99之前:元素数量必须是编译时刻确定的字面量 

数组的初始化

数组初始化实例
  //完全初始化
int a[5] = {0,1,2,3,4};
//不完全初始化
int b[5] = {0,1,2};
/*不完全初始化情况下,其它值为0*/
//还可以这样定义数组
int c[] = {0,1,2,3,4};
for (int i = 0; i<5;i++) {
printf("c[i] = %d\n",c[i]);
}
//下面为错误示范
/*
{
int a[5];
a[5] = {1,2,3,4,5};
*/

集成初始化时的定位(C99 only)

int a[10] = {[0] = 2, [2] = 3, 5,};

数组

  • 是一种容器(放东西的东西)
  • 其中所有的元素具有相同的数据类型;
  • 一旦创建,不能改变大小
  • *(数组中的元素在内存中是连续依次排列的)

int a[10]

  • 一个int的数组
  • 10个单元:a[0],a[1],a[2],....,a[9]
  • 每个单元就是一个int类型的变量
  • 可以出现在赋值的左边或右边
    • a[2] = a[1] + 6;
  • 在赋值号左边的叫左值

数组的单元

  • 数组的每个单元就是数组类型的一个变量
  • 使用数组时放在[]中的数字叫做下标或索引,下标从0开始计数:
    • grades[0]
    • grades[99]

有效的下标范围

  • 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写
  • 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃
    • segmentation fault
  • 但是也可能运气好,没造成严重的后果
  • 所以这是程序员的责任来保证程序只使用有效的下标值:[0,数组的大小-1]

二维数组

  • int a[3][5];
  • 通常理解为a是一个3行5列的矩阵

二维数组的遍历

二维数组的遍历
 int a[3][5];
int i ,j;
for (i=0;i<3;i++){
for(j=0;j<5;j++){
a[i][j] = i*j;
}
}

二维数组的初始化

int a[][5] = {
{0,1,2,3,4},
{2,3,4,5,6},
} ;
  • 列数是必须给出的,行数可以由编译器来数
  • 每行一个{},逗号分隔
  • 最后的逗号可以存在,有古老的传统
  • 如果省略,表示补零
  • 也可以使用定位(*C99 ONLY)
以井字棋为例
 #include <stdio.h>
int main()
{
const int size = 3;
int board[size][size];
int i, j;
int numOfX;
int numOfO;
int result = -1; //-1:没人赢,1:X赢,0:O赢
//读入矩阵
for (i = 0; i < size; i++) {
for (j = 0; j < size; j++) {
scanf_s("%d", &board[i][j]);
}
}
//检查行
for (i = 0; i < size && result == -1; i++) {
numOfO = 0, numOfX = 0;
for (j = 0; j < size; j++) {
if (board[i][j] == 1) {
numOfX++;
}
else {
numOfO++;
}
}
if (numOfX == size) {
result = 1;
}
else if (numOfO == size) {
result = 0;
}
}
//检查列
for (j = 0; j < size && result == -1; j++) {
numOfO = 0, numOfX = 0;
for (i = 0; i < size; i++) {
if (board[i][j] == 1) {
numOfX++;
}
else {
numOfO++;
}
}
if (numOfX == size) {
result = 1;
}
else if (numOfO == size) {
result = 0;
}
}
//正对角线的检查
numOfO = 0, numOfX = 0;
for (i = 0; i < size; i++) {
if (board[i][i] == 1) {
numOfX++;
}
else {
numOfO++;
}
}
if (numOfX == size) {
result = 1;
}
else if (numOfO == size) {
result = 0;
}
//反对角线的检查
numOfO = 0, numOfX = 0;
for (i = 0; i < size; i++) {
if (board[i][size-i-1] == 1) {
numOfX++;
}
else {
numOfO++;
}
}
if (numOfX == size) {
result = 1;
}
else if (numOfO == size) {
result = 0;
}
printf("result = %d\n", result);
return 0;
}

数组的大小

  • sizeof给出整个数组说占据的内容的大小,单位是字节
  • sizeof(a)/sizeof(a[0])

数组的赋值

  • 数组变量本身不能被赋值
  • 要把一个数组的所有元素交给另一个数组,必须采用遍历
  • 通常用for循环
数组的赋值实例
 int a[10] = {[0] = 2, [2] = 3, 5,};
int b[10];
for (int i = 0; i<sizeof(a)/sizeof(a[0]) ; i++){
b[i] = a[i];
}

数组作为函数参数时,往往必须再用另一个参数来传入数组的大小

  • 数组作为函数的参数时:
  • 不能在[]中给出数组的大小
  • 不能再利用sizeof来计算数组的元素个数!
数在数组里的查找
 #include <stdio.h>
int search(int key ,int a[] ,int length);
int main()
{
//查找key在数组a中的位置
int a[] = {2,4,6,7,1,3,5,9,11,13,23,14,32,};
int x;
int loc;
printf("请输入一个数字:");
scanf("%d",&x);
loc = search(x,a,sizeof(a)/sizeof(a[0]));
if(loc != -1){
printf("%d在第%d个位置上\n",x,loc);
}else{
printf("%d不存在",x);
}
return 0;
}
int search(int key ,int a[] ,int length)
{
int ret = -1;
int i;
for (i = 0; i<length; i++){
if( a[i] == key){
ret = i;
break;
}
}
return ret;
}

搜索

  • 最简单的方法:遍历
  • 一专多能是不好的代码 
搜索实例:二分搜索
  int search(int key,int a[], int len)
{
int ret = -1;
int left = 0;
int right = len-1;
while(left<right)
{
int mid = (left+right)/2;
if( a[mid] == k)
{
ret = mid;
break;
}else if( a[mid] > k){
right = mid - 1;
}else if( a[mid] < k){
left = mid + 1;
}
}
return ret;
}
选择排序(排序是二分搜索的前提)
 #include <stdio.h>
int max(int a[], int len);
int main()
{
int a[] = {34,3,34,86,576,45,213,4,6,461,21,34,452,12,45,21};
int len = sizeof(a)/sizeof(a[0]);
for( int i = len - 1; i > 0; i--)
{
int maxid = max(a,i+1);
//交换 位置
int t = a[maxid];
a[maxid] = a[i];
a[i] = t;
}
//输出检验
for(int i = 0;i<len; i++){
printf("%d ",a[i]);
}
return 0;
}
int max(int a[], int len)
{
int maxid = 0;
for(int i = 1; i<len; i++ ){
if (a[i] > a[maxid])
{
maxid = i;
}
}
return maxid;
}

函数

函数是一块代码,接收0个或多个参数,做一件事情,并返回0个或1个值

 

  • 函数头:<返回类型><函数名><参数表>
  • 函数体

调用函数

  • 函数名(参数表)
  • ()起到了表示函数调用的重要作用
  • 即使没有参数也需要()
  • 如果有参数,则需要给出正确的数量和顺序
  • 这些值会被按照顺序依次用来初始化函数中的参数 

从函数中返回值

  • 可以赋值给变量
  • 可以再传递给函数
  • 甚至可以丢弃

 没有返回值的函数

  • void函数名(参数表)
  • 不能使用带值的return 
  • 可以没有return
  • 调用的时候不能做返回值的赋值

函数先后关系

  • 像这样把sum()写在上面,是因为:
  • C的编译器自上而下顺序分析你的代码
  • 在看到sum(1,10)的时候,它需要知道sum()的样子
  • 也就是sum()要几个参数,每个参数的类型如何(int,double),返回什么类型

函数的原型声明

  • 函数头,以分号“;”结尾,就构成了函数的原型
  • 函数原型的目的是告诉编译器这个函数长什么样子
    • 名称
    • 参数(数量及类型)
    • 返回类型
  • 旧标准习惯把函数原型写在调用它的函数前面,现在一般写在调用它的函数前面
  • 原型里可以不写参数的名字,但是一般仍然写上

调用函数

  • 如果函数有参数,调用函数时必须传递给它数量、类型正确的值
  • 可以传递给函数的值是表达式的结果,这包括:
    • 字面量
    • 变量
    • 函数的返回值
    • 计算的结果

类型不匹配

  • 编译器会悄悄替你把类型转换好,但是这很可能不是你所期望的

传值

  • 每个函数有自己的变量空间,参数也位于这个独立的空间中,和其它函数没有关系
  • 过去,对于函数参数表中的参数,叫做“形式参数”,调用函数时给的值,叫做“实际参数”
  • 由于容易让初学者误会实际参数就是实际在函数中进行计算的参数,误会调用函数的时候把变量
    而不是值传进去了,所以我们不建议继续用这种古老的方式来称呼它们
  • 我们认为,它们是参数和值的关系

本地变量

  • 函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作本地变量
  • 定义在函数内部的变量就是本地变量
  • 参数也是本地变量

变量的生存期和作用域

  • 生存期:什么时候这个变量开始出现了,到什么时候它消亡了
  • 作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)
  • 对于本地变量,这两个问题的答案是统一的:大括号内——块

本地变量的规则

  • 本地变量是定义在块内的
    • 它可以是定义在函数的块内
    • 也可以定义在语句的块内
    • 甚至可以随便拉一对大括号来定义变量
  • 程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了
  • 块外面定义的变量在里面仍然有效
  • 块里面定义了和外面同名的变量则掩盖了外面的
  • 不能在一个块内定义同名的变量
  • 本地变量不会被默认初始化
  • 参数在进入函数时就会初始化

没有参数时

  • f(void)

函数里的函数

  • C语言不允许函数嵌套定义

 


指针

运算符&

  • scanf("%d",&i);里的&
  • 获得变量的地址,它的操作数必须是变量
  • int i;printf("%x",&i);
  • 地址的大小是否与int相同取决于编译器
  • int i;printf("%p",&x);

&不能取的地址

  • &不能对没有地址的东西取地址
    • &(a+b)? error
    • &(a++)?error
    • &(++a)?error

scanf

  • 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量?
  • scanf("%d",&i);
  • scanf()的原型应该是怎样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?

指针

  • 就是保存地址的变量
    • int i;
    • int* p = &i;
    • int* p,q;
    • int *p,q;
    • int *p,*q;

  指针变量

  • 变量的值是内存的地址
  • 普通变量的值是实际的值
  • 指针变量的值是具有实际值的变量的地址

作为参数的指针

  • void f(int *p);
  • 在被调用的时候得到了某个变量的地址:
  • int i=0;f(&i);
  • 在函数里面可以通过这个指针访问外面的这个i

访问那个地址上的变量*

  • *是一个单目运算符,用来访问指针的值所表示的地址上的变量
  • 可以做右值也可以做左值
  • int k = *p;
  • *p = k+1;

*左值之所以叫左值

  • 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果:
  • a[0] = 2;
  • *p = 3;
  • 是特殊的值,所以叫左值

 指针的运算符&*

  • 互相反作用
    • *&yptr -> *(&yptr) -> *(yptr的地址)-> 得到那个地址的变量 -> yptr
    • &*yptr -> &(*yptr) -> &(y)-> 得到y的地址,也就是yptr -> yptr

指针应用场景

  • 交换两个变量的值
  •  
  • 函数返回多个值,某些值就只能通过指针返回
  • 传入的参数实际上是需要保存带回的结果的变量
  •  
  • 函数返回运算的状态,结果通过指针返回
  • 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错
  • -1或0(在文件操作会看到大量的例子)
  • 但是当任何数值都是有效的可能结果时,就得分开返回了
  • 后续的语言(C++,Java)采用了异常机制来解决这个问题 

指针常见错误

  • 定义了指针变量,还没有指向任何变量,就开始使用指针

传入函数的数组 成了什么?

  • 函数参数表中的数组实际上是指针
  • sizeof(a) == sizeof(int*)
  • 但是可以用数组的运算符[]运算

数组变量是特殊的指针

  • 数组变量本身表达地址,所以
    • int a[10];int *p = a;//无需用&取地址
    • 但是数组的单元表达的是变量,需要用&取地址
    • a == &a[0]
  • []运算符可以对数组做,也可以对指针做
  • p[0] <==> a[0]
  • *运算符可以对指针做,也可以对数组做
  • 数组变量是const的指针,所以不能被赋值

指针与const

指针是const

  • 表示一旦得到了某个变量的地址,不能再指向其它变量
  • int *const q = &i;      //指针q是const,不能再指向其它变量
    • *q  = 26;  //OK
    • q++;   //ERROR

所指是const

  • 表示不能通过这个指针去修改那个变量(并不能使得那个变量变成const)
  • const int *p = &i; 
    • *p这个值是const ,不允许被修改,它的值确定,但是可以修改它所指向的对象 及 更改它指向的对象
    • *p = 26;  //ERROR! (*p)是const
    • i = 26;  //OK
    • p = &j;      //OK

判断那个被const了的标志是const在*的前面还是后面

转换

  • 总是可以把一个非const的值转换成cosnt的
    • void f (const int* x);  
    • int a = 15;
    • f (&a);    //ok
    • const int b = a;
    • f (&b);     //ok
      • {b = a + 1;}  //ERROR!
  • 当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改

const数组

  • const int a[] = {1,2,3,4,5,6,};
  • 数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
  • 所以必须通过初始化进行赋值

保护数组值

  • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
  • 为了保护数组不被函数破坏,可以设置参数为const
  • int sum(const int a[], int length); 

指针+1

  • 给一个指针加1表示要让指针指向下一个变量
    • int a[10];  
    • int *p = al
    • *(p + 1) -> a[1]
    • *(p+n) <-> a[n]
  • 如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义

指针运算

  • 这些算数运算可以对指针做:
    • 给指针加、减一个整数(+,+=,-,-=)  
    • 递增递减(++/--)
    • 两个指针相减(两个地址的距离)

*p++

  • 取出p所指的那个数据来,完事后顺便把p移到下一个位置去
  • *的优先级虽然高,但是没有++高
  • 常用于数组类的连续空间操作
  • 在某些CPU上,这可以直接被翻译成一条汇编指令

指针比较

  • <,<=,==,>,>=,!=都可以对指针做
  • 比较它们在内存中的地址
  • 数组中的单元的地址肯定是线性递增的

0地址

  • 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  • 所以你的指针不应该具有0植
  • 因此可以用0地址来表示特殊的事情:
    • 返回的指针是无效的  
    • 指针没有被真正初始化(先初始化为0)
  • NULL是一个预定定义的符号,表示0地址
  • 有的编译器不愿意你用0来表示0地址

指针的类型

  • 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
  • 但是指向不同类型的指针是不能直接互相赋值的
  • 这是为了避免用错指针

指针的类型转换

  • void*表示不知道指向什么东西的指针
  • 计算时与char*相同(但不相通)
  • 指针也可以转换类型
    • int *p = &i;void*q = (void*)p;  
    • 这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
    • 我不再当你是int啦,我认为你就是个void!

用指针来做什么

  • 需要传入较大的数据时用作参数
  • 传入数组后对数组做操作
  • 函数返回不止一个结果
  • 需要用函数来修改不止一个变量
  • 动态申请的内存

 

 

 

 

 

 

 

 

posted @   我千五可以  阅读(233)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示