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 @ 2022-08-29 08:57  我千五可以  阅读(249)  评论(0)    收藏  举报