C语言学习进程(翁恺)
# include <stdio.h> int main () { return 0; }
例:Hello World
#include <stdio.h> int main() { printf("Hello World!\n"); return 0; }
一、整型数据(整数)
- 两个整数的运算只能是整数
- 整数计算没有误差
- 一般用int
二、浮点型数据(浮点数)
- 浮点数 double 输入%lf 输出%f
- 浮点数存在误差
- 有float,double ,long double
- 可以用指数形式表示
三、字符型数据(ASCII码)
- 用char表示
- 可以解释为整数,也可以解释为字符
- 内部表示按照字符的编码(ASCII码)
变量定义一般形式
类型 标识符列表
标识符:英文字母,数字,下划线
变量初始化
定义常量 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
- 运算符 + - * / % = ……
- 算子:参与运算的值,例如,
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; }
一、输入函数
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; }
三、输入输出格式说明
输入输出数据的格式需要用格式字符加以区分
if
格式字符 | 说明 |
%d | 以十进制形式输出整型数据 |
%o | 以无符号八进制形式输出整型数据 |
%X,%x | 以无符号十六进制形式输出整型数据(%X时字母大写) |
%c | 以字符型式输出,只输出一个字符 |
%s | 输出字符串 |
%f | 以小数形式输出浮点数(默认6位小数) |
- 有六个关系运算符
关系运算结果成立则为1,否则为0
关系表达式就是含有关系运算符的表达式
关系运算符
关系运算符 == 相等 != 不相等 > 大于 >= 大于或等于 < 小于 <= 小于或等于 注意其中有两个字符的运算符:==、>=和<=的两个字符必须紧紧连在一起,中间不能插入空格。 关系运算存在优先级:判断是否相等的 == 和 != 的优先级更低 运算关系:从左到右 关系运算结果成立则为1,否则为0 example:printf(" %d", 7 >= 3+4);
含有逻辑运算符的表达式称为逻辑表达式
C语言中有三个逻辑运算符,即&&、||、!
注意&&、||
为双目运算符,!
为单目运算符
逻辑表达式的结果为0或1
逻辑运算
自左向右,如果左边的结果可以决定结果了, 就不会做右边的计算
结果是0 或 1
运算符 例 ! !a && a && b || a || b
if(表达式) //若表达式结果非0,执行语句一
语句一;
else
语句二;
if...else...实例
if ( ){ }else { } if (score < 60 ) printf(" not pass); else printf(" pass "); //最好加上{}
// 初始化 int price = 0; /* 42 */
if 再探
if...else...的嵌套使用
用于区分多种情况
if...else...的嵌套使用
if (){ }else if () { }else if () { }else { }
循环体内要有打破循环的条件
while ( ){ ; ; } 判断,再执行
do { <循环体语句> }while( );
for 念做 对于 ( ; ; )for 中的可以省略 for ( <初始条件> ; <循环继续条件> ; <每轮动作> ){ }
等差数列求和
# 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 ( type ){ case 1: printf("hello\n"); break; case 2: break; default: break; }
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;
// 如何用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;
// 如何用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;
输入三个整数 ,输出其中 最小数
# 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!
用指针来做什么
- 需要传入较大的数据时用作参数
- 传入数组后对数组做操作
- 函数返回不止一个结果
- 需要用函数来修改不止一个变量
- 动态申请的内存
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)