程序设计入门—C语言(MOOC—翁恺)

文章目录

  • 程序设计入门—C语言(MOOC—翁恺)
    • Week 1 程序设计与C语言
      • lesson 1 计算机和编程语言
        • 辗转相除法
        • 程序的执行
      • lesson 2 第一个程序
    • Week 2 计算
      • lesson 1 变量
        • 变量的定义及初始化
        • 变量类型
        • ※ 关于scanf
        • 常量
      • lesson 2 数据类型
      • lesson 3 表达式
    • Week 3 判断与循环
      • lesson 1 判断
      • lesson 2 循环
    • Week 4 进一步的判断和循环
      • lesson 1 逻辑类型与运算
        • bool 布尔类型
        • 逻辑运算
        • 短路运算
        • 条件运算符
        • 逗号表达式
      • lesson 2 级联和嵌套的判断
      • lesson 3 多路分支——switch-case语句
      • lesson 4 循环的例子
        • 求平均数
        • 猜数字
        • 整数的分解与逆序
    • Week 5 循环控制
      • lesson 1 示例:求验素数
      • lesson 2 多重循环
        • 接力break——传统跳出方法
        • goto语句——高级跳出方法
    • Week 6 数组与函数
      • lesson 1 数组
      • lesson 2 函数的定义与使用
        • 求素数的和
        • 函数声明
        • 函数定义
        • 函数调用
        • 函数返回
        • 再次强调单一出口
      • lesson 3 函数的参数与变量
        • 函数原型
        • 参数传递
        • 本地变量/局部变量/自动变量
        • 注意点
        • 小练习
      • lesson4 二维数组
        • 二维数组的初始化
    • Week 7 数组运算
      • lesson 1 数组运算
        • 集成初始化时的定位
        • 数组的大小
        • 数组的赋值
        • 实例1:找数字
        • 实例2:求素数
        • 实例3:构造 n 以内(不含n)的素数表
      • lesson 2 搜索/查找
      • lesson 3 排序初步
    • Week 8 指针与字符串
      • lesson 1 指针
        • 指针
        • 指针变量
        • 作为参数的指针
        • 传入函数的数组
        • 数组参数
        • 数组变量
      • lesson 2 字符类型
        • char类型
        • 大小写转换
      • lesson 3 字符串
        • 字符串的概念与性质
        • 字符串常量
        • 字符串变量
        • 字符串用指针还是数组
        • clar* 是字符串吗
        • 小练习
      • lesson 4 字符串计算
        • 字符串赋值
        • 字符串输入输出
        • 字符串输入常见错误
        • 空字符串
        • 字符串函数(string.h)

程序设计入门—C语言(MOOC—翁恺)

Week 1 程序设计与C语言

lesson 1 计算机和编程语言

  • 告诉人做一件事情是 what to do
  • 告诉计算机做一件事情是 how to do
  • 编程语言并非人与计算机交流的语言,人与计算机通过人机交互进行交流

辗转相除法

int u = 32;
int v = 26;
while ( v != 0 ) {
    int temp = u % v;
    u = v;
    v = temp;
}
printf("%d\n", u);

程序设计入门—C语言(MOOC—翁恺)_第1张图片

  1. 如果 v 等于 0 ,计算结束,u 就是最大公约数
  2. 如果 v 不等于 0 ,那么计算 u 除以 v 的余数,让 u 等于 v ,而 v 等于那个余数
  3. 回到第一步

程序的执行

  • 解释:借助一个程序,那个程序能试图理解你的程序,然后按照你的要求执行
  • 编译:借助一个程序,就像一个翻译,把你的程序翻译成计算机能懂的机器语言程序并可直接执行

lesson 2 第一个程序

  • 程序框架
#include 
int main()
{
	//代码
	return 0;
}
  • 在接触函数之前,所有代码都是这个框架

Week 2 计算

lesson 1 变量

变量的定义及初始化

  • <类型名称> <变量名称> = <初始值>;
    • int price = 0;
    • int amount = 100;
  • 定义组合变量时,也可在定义中单独给单个变量赋初值
    • int price = 0, amount = 100;
  • C语言严格区分大小写
  • 变量的命名规则: 一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)

变量类型

  • int price = 0;,这一行定义了一个变量,变量名是price,类型是int,初始值是0
  • C是一种强类型语言,所有变量在使用之前必须定义或声明,且必须具有确定的数据类型。数据类型表示在变量中可以存放且只能存放什么类型的数据,程序运行过程中也不能改变变量的类型
    程序设计入门—C语言(MOOC—翁恺)_第2张图片
    程序设计入门—C语言(MOOC—翁恺)_第3张图片

※ 关于scanf

scanf里不能有\n,否则当输入完需要输入的内容后就算按无数回车也必须要再输入一个值才能成功输入,但该值并不会参与程序的运行

常量

  • 一种定义方式(C99 ONLY):const int AMOUT = 100;
  • const 是一个修饰符,加在 int 前面,用来给这个常量加上一个const(不变)的属性,这个const的属性表示这个变量的值一旦初始化就不能更改

lesson 2 数据类型

  • 浮点数:带小数点的数值
    • 浮点的本意就是指小数点是浮动的,是计算机内部表达非整数(包括分数和无理数)的一种方式,另一种方式叫定点数,但C语言中没有定点数
  • double
    • printf("%f", ...);
    • scanf("%lf", ...);

lesson 3 表达式

  • 计算时间差
    int hour1, minute1;
    int hour2, minute2;
    
    scanf("%d %d", &hour1, &minute1);
    scanf("%d %d", &hour2, &minute2);
    
    int t1 = hour1 * 60 + minute1;
    int t2 = hour2 * 60 + minute2;
    
    int t = t1 - t2;
    
    printf("时间差是%d小时%d分", t / 60, t % 60);
    
    程序设计入门—C语言(MOOC—翁恺)_第4张图片
  • hour * 60 + minute → 转换为分钟为单位
  • t / 60 → 小时,t % 60 → 分钟

  • a++(- -) 是 a 加(减)1 之前的值,在使用i之后,使i的值加(减)1 ,先使用再运算
  • ++(- -)a 是 a 加(减)1 之后的值,在使用i之前,先使i的值加(减)1 ,先运算再使用
  •   int a = 3;
      int c1, c2, c3, c4;
      
      c1 = a++;  // a赋值给c1后自身再加1
      printf("c1的值是%d,a的值是%d\n", c1, a);
      
      a = 3;
      c2 = ++a;  // a自身加1后再赋值给c2
      printf("c2的值是%d,a的值是%d\n", c2, a);
      
      a = 3;
      c3 = a--;  // a赋值给c3后自身再减1
      printf("c3的值是%d,a的值是%d\n", c3, a);
      
      a = 3;
      c4 = --a;  // a自身减1后再赋值给c4
      printf("c4的值是%d,a的值是%d\n", c4, a);
    
    程序设计入门—C语言(MOOC—翁恺)_第5张图片

Week 3 判断与循环

lesson 1 判断

  • C语言提供了六个关系运算符:

    • == 相等
    • != 不相等
    • > 大于
    • >= 大于或等于
    • < 小于
    • <= 小于或等于
  • 注意其中有两个字符的运算符:==、>=和<=的两个字符必须紧紧连在一起,中间不能插入空格。

  • 关系运算的结果是一个逻辑值,逻辑值只有两种可能的值:true(真,表示成立)或 false(假,表示不成立)。当两个值的关系符合关系运算符的预期时,关系运算的结果为 true,否则为 false。

  • 当两个值的关系符合运算符的预期时,关系运算的结果为整数1,否则为整数0

    printf("%d\n", 5 == 3);
    printf("%d\n", 5 != 3);
    printf("%d\n", 5 > 3);
    printf("%d\n", 5 >= 3);
    printf("%d\n", 5 < 3);
    printf("%d\n", 5 <= 3);
    

    程序设计入门—C语言(MOOC—翁恺)_第6张图片


  • 比较两个数的大小
  • 代码一:
    int a, b;
    printf("请输入两个整数:");
    scanf("%d %d", &a, &b);
    int max = 0;
    if ( a > b )
    	max = a;
    else
        max = b;
    printf("大的那个是%d\n", max);
    
    程序设计入门—C语言(MOOC—翁恺)_第7张图片
  • 代码二:
    int a, b;
    printf("请输入两个整数:");
    scanf("%d %d", &a, &b);
    int max = b;
    if ( a > b )
    	max = a;
    printf("大的那个是%d\n", max);
    
    程序设计入门—C语言(MOOC—翁恺)_第8张图片

  • if( 1<=n<=10 ); 可以通过编译,但不表示 n 属于 [1,10]
    • 先判断 1<=n ,判断结果为0(假)或1(真),然后在判断 (1<=n) 的整体值(0或1)是否小于等于 10,结果为真
    • 可以执行,但并不能表示 n 介于 1 到 10 之间的判断
    • 如果要执行 n 介于 1 到 10 之间的判断,应表示为:if( n>=1 && n<=10 )

lesson 2 循环

  • while语句 是一个循环语句,它会首先判断一个条件是否满足,如果条件满足,则执行后面紧跟着的语句或语句括号,然后再次判断条件是否满足,如果条件满足则再次执行,直到条件不满足为止。后面紧跟的语句或语句括号,就是循环体
  • do-while循环 和 while循环很像,唯一的区别是我们在循环体执行结束的时候才来判断条件。也就是说,无论如何,循环都会执行至少一遍,然后再来判断条件。与while循环相同的是,条件满足时执行循环,条件不满足时结束循环
  • do-while循环结尾有分号

  • for(a=10;a>0;a--)
    表示:对于一开始的 a=10,当 a>0 时,重复执行循环体,每一轮执行完循环体时使得 a- -

  • 做求程序时,变量初始值应为0
  • 做求程序时,变量初始值应为1

  • 在循环语句内定义循环变量(C99 ONLY)

  • 每个for循环都可转换为一个while循环:
for ( int i=1; i<=n; i++ ) {
	fact *= i;
}

//等同于

int i = 1;
while ( i<=n ) {
    fact *= i;
    i++;
}
  • 循环语句的选择:
    • 若有固定次数:用for
    • 若必须执行一次(至少执行一次):用do-while
    • 其他情况用while

Week 4 进一步的判断和循环

lesson 1 逻辑类型与运算

bool 布尔类型

//导入 stdbool.h 来使用布尔类型,C99 ONLY
#include 
#include 

//计算n!,n的值在main中定义
int main(void)
{
    int n = 10;    //计算叠乘数
    int sum = 1; //用来存放叠乘的结果
    bool flag = false;    //叠乘标记
    
    int num = n;    //循环次数
    while ( !flag ) {
        sum = sum * (num--);
        //当num=1时结束循环
        if ( num == 1 ) {
            flag = true;
        }
    }
    printf ("%d的叠乘值为 %d \n", n, sum);
    return 0;
}

运行结果

逻辑运算

  • 逻辑运算是对逻辑量进行的运算,结果只有0或1
  • 逻辑量是关系运算或逻辑运算的结果
运算符 描述 示例 结果
! 逻辑非 ! a 真假颠倒
&& 逻辑与 a && b 全真才真,一假即假
|| 逻辑或 a || b 一真即真,全假才假
  • 如何表达x ∈ (4, 6)
    • 4 < x < 6是错误的表达方式,因为x < 4表示的是一个逻辑值 0 或 1
    • 正确的表达方式是:x < 6 && x > 4
  • x ∈ [4, 6]同理

短路运算

  • 逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算

  • a == 6 && b == 1

  • a == 6 && b += 1,如果左边为假,右边的赋值就不会进行

    切记! 不要把赋值包括复合赋值写进逻辑运算表达式

  • 对于 && ,左边是 false 时就不会进行右边

  • 对于 ||,左边是 ture 时就不会进行右边

条件运算符

  • count = (count > 20) ? count - 10 : count + 10;
  • 条件 ?条件满足时的值 :条件不满足的值
  • 自右向左结合
  • 忌用嵌套的条件表达式

逗号表达式

  • 连接两个表达式
  • 用右边的表达式作为结果
    • i = (3 + 4, 5 + 6),i 的值是11

lesson 2 级联和嵌套的判断

  •   //单一出口,f的值可灵活使用,并且程序修改相对容易,推荐
      int f;
      if ( x < 0 ) {
      	f = -1;
      } else if ( x == 0 ) {
      	f = 0;
      } else {
      	f = 2 * x;
      }
      printf("%d",f);
      
      /**********************************************/
      
      //写死了,不推荐
      if ( x < 0 ) {
      	printf("%d", -1);
      } else if ( x == 0 ) {
      	printf("%d", 0);
      } else {
      	printf("%d", 2 *x);
      }
    

lesson 3 多路分支——switch-case语句

//if-else语句
if ( type == 1 )
	printf("你好");
else if ( type == 2 )
	printf("早上好");
else if ( type == 3 )
	printf("晚上好");
else if ( type == 4 )
	printf("再见好");
else
	printf("Ahh?What?");

/**********************************************/

//转为switch-case语句(控制语句)
//type的值只能是int类型
switch(type){
	case 1:
		printf("你好");
	case 2:
		printf("早上好");
	case 3:
		printf("晚上好");
	case 4:
		printf("再见好");
	default:
		printf("Ahh?What?");
}

switch语句可以看做是一种基于运算的跳转,计算控制表达式的值后,程序会跳转到相匹配的case(分支标号)处,分支标号只是说明switch内部位置的路标,在执行完分支中的最后一条语句后,如果后面没有break,就会顺利执行下面的case里去,直到遇到 break 或是 switch结束为止

lesson 4 循环的例子

求平均数

int number;
int sum = 0;
int count = 0;
scanf("%d", &number);
while ( number != -1 ) {
	sum += number;
	count++;
	scanf("%d", &number);
}
printf("%f\n", 1.0 * sum / count);

程序设计入门—C语言(MOOC—翁恺)_第9张图片

猜数字

#include 
#include 
#include 

int main()
{
    srand(time(0));		//!!!使用该语句要引入time.h头文件
    int number = rand() % 100 + 1;	//rand()是C语言标准可函数,得到一个随机数
                                    //x % n 得到的是一个 [0, n-1] 的数
    int count = 0;
    int a = 0;
    printf("这里有一个在1到100之间的正整数\n");
    do{
        printf("猜一猜这个数:");
        scanf("%d", &a);
        count++;
        if ( a > number ) {
            printf("大了\n");
        } else if ( a < number ) {
            printf("小了\n");
        }
    }while( a != number );
    printf("真腻害,你用了%d次就猜到了答案\n", count);
    return 0;
}

程序设计入门—C语言(MOOC—翁恺)_第10张图片

整数的分解与逆序

int x = 12345;
int digit;
int ret = 0;
while ( x > 0 ) {
    digit = x % 10;
    //printf("%d", digit);
    ret = ret * 10 + digit;
    printf("x=%d,digit=%d,ret=%d\n", x, digit, ret);
    x /= 10;
}
printf("%d", ret);
/********************************************************************/
int x = 700;
int digit;
//int ret = 0;
while ( x > 0 ) {
    digit = x % 10;			//每一次循环摘出原数字的最后一位
    printf("%d", digit);	//每一次循环打印出原数字的最后一位作为新数字第一位的下一位
    //ret = ret * 10 + digit;
    //printf("x=%d,digit=%d,ret=%d\n", x, digit, ret);
    x /= 10;
}
//printf("%d", ret);

Week 5 循环控制

lesson 1 示例:求验素数

int x;
scanf("%d", &x);

int i;
int isPrime = 1;

for ( i = 2 ; i < x ; i++ ) {
    if ( x % i == 0 ) {
        isPrime = 0;
        break;
    }
}

if ( isPrime == 1 ) {
    printf("是素数\n");
} else {
    printf("不是素数\n");
}

程序设计入门—C语言(MOOC—翁恺)_第11张图片

lesson 2 多重循环

  • 计数器的灵活运用
  • break 和 continue 只对其所在的那一层循环起作用

接力break——传统跳出方法

int x;
int one, two five;
int exi = 0;

scanf("%d", &x);
for ( one = 1 ; one < x*10 ; one++ ) {
    for ( two = 1 ; two < x*10/2 ; two++ ) {
        for ( five = 1 ; five < x*10/5 ; five++ ) {
			if ( one + two*2 + five*5 == x*10 ) {
                printf("可以用%d个一角+%d个两角+%d五角得到%d元\n", 
                      one, two, five, x);
                exit = 1;
                break;
            }
        }
        if ( exit == 1 ) break;
    }
    if ( exit == 1 ) break;
}

goto语句——高级跳出方法

int x;
int one, two five;

scanf("%d", &x);
for ( one = 1 ; one < x*10 ; one++ ) {
    for ( two = 1 ; two < x*10/2 ; two++ ) {
        for ( five = 1 ; five < x*10/5 ; five++ ) {
			if ( one + two*2 + five*5 == x*10 ) {
                printf("可以用%d个一角+%d个两角+%d五角得到%d元\n", 
                      one, two, five, x);
                goto out;
            }
        }
    }
}
out:
return 0;

goto语句名声不好,少用

Week 6 数组与函数

lesson 1 数组

数据可以存放在变量里,每一个变量有一个名字,有一个类型,还有它的生存空间。如果我们需要保存一些相同类型、相似含义、相同生存空间的数据,我们可以用数组来保存这些数据,而不是用很多个独立的变量。数组是长度固定的数据结构,用来存放指定的类型的数据。一个数组里可以有很多个数据,所有的数据的类型都是相同的。

  • 计算输入的数字的平均数并输出所有大于平均数的数
int main(void)
{
    int x;
    double sum = 0;
    int cnt = 0;
    int number[100];	//定义数组	//危险的定义方法,输入可能超过100
                        //解决方法:令cnt不能超过100 或 用变量(C99 ONLY)
    scanf("%d", &x);
    while ( x != -1 ) {
        number[cnt] = x;	//给数组赋值
        sum += x;
        cnt++;
        scanf("%d", &x);
    }
    if ( cnt > 0 ) {
        printf("%.2f\n", sum / cnt);
        int i;
        for ( i = 0; i < cnt; i++ ) {
            if ( number[i] > (sum / cnt ))	{	//遍历数组中的元素
                printf("%d\n", number[i]);	//使用数组中的元素
            }
        }
    }
    return 0;
}

程序设计入门—C语言(MOOC—翁恺)_第12张图片

  • 定义数组

    • <类型> 数组名称[数组元素数量];
    • 元素必须是整数
    • C99之前:元素数量必须是编译时刻的字面量,不能是变量
  • 数组的下标必须是整数,因此将字符用单引号表示也可以做数组下标,比如:

    • int a[255];
    • a['A'] = 1;
  • 容器是现代程序设计当中的一个重要基本概念,现代的编程语言都应该提供某种形式的容器,语言 提供容器的能力 以及 所提供的容器能力 的大小是其评判其综合能力的一个重要指标

  • 数组中的元素在内存中是连续排列的

  • 数组元素可以出现在赋值运算符的左边或右边,在赋值左边叫左值,在右边就叫右值

  • 有效的下标范围

    • 无论是对数组单元做读还是写,编译器和运行环境都不会检查数组下标是否越界
    • 一旦程序运行,越界的数组访问可能造成问题而导致程序崩溃
    • 崩溃错误提示:segmentation fault(Windows)
    • 也可能运气好,没造成严重后果
    • 应该保证程序只使用有效的下标值范围:[0, 数组的大小-1]
  • 写一个程序,输入数量不确定的 [0, 9] 范围内的整数,统计每个数字出现的次数,输入 -1 表示结束

    //用数组做散列计算
    int main(void)
    {
        const int number = 10;	//数组的大小
        int x;
        int count[number];		//定义数组
        int i;
        
        for ( i = 0; i < number; i++ ) {	//遍历并初始化数组
            count[i] = 0;
        }
        scanf("%d", &x);
        while ( x != -1 ) {
            if ( x >= 0 && x <= 9 ) {
                count[x]++;		//数组参与运算
            }
            scanf("%d", &x);
        }
        for ( i = 0; i < number; i++ ) {
            printf("%d:%d\n", i , count[i]);
        }
        return 0;
    }
    

    程序设计入门—C语言(MOOC—翁恺)_第13张图片

lesson 2 函数的定义与使用

求素数的和

//原程序(无自定义函数)
int main()
{
    int m, n;
    int sum = 0;
    int cnt = 0;
    int i;
    
    scanf("%d %d", &m, &n);
    if ( m = 1 ) {
        m = 2;
    }
    for ( i = m; i <= n; i++) {
        int isPrime = 1;
        int k;
        for ( k = 2; k < i - 1; k++ ) {
            if ( i % k == 0 ) {
                isPrime = 0;
                break;
            }
        }
        if ( isPrime ) {
            sum += i;
            cnt++;
        }
    }
    printf("%d %d\n", cnt, sum);
    
    return 0;
}

/********************************************************/

//改进程序(有自定义函数)
int isPrime(int i)		//判断 i 是不是素数
{
    int ret= 1;
        int k;
        for ( k = 2; k < i - 1; k++ ) {
            if ( i % k == 0 ) {
                ret = 0;
                break;
            }
        }
    return ret;
}

int main()
{
    int m, n;
    int sum = 0;
    int cnt = 0;
    int i;
    
    scanf("%d %d", &m, &n);
    if ( m = 1 ) {
        m = 2;
    }
    for ( i = m; i <= n; i++ ) {
    	if ( isPrime(i) ) {
            sum += i;
            cnt++;
        }
    }
    printf("%d %d\n", cnt, sum);
    
    return 0;
}

函数声明

  • 函数定义在主函数之前,不用声明
  • 函数定义在主函数之后,需要声明

函数定义

程序设计入门—C语言(MOOC—翁恺)_第14张图片

函数调用

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

函数返回

  • 函数知道每一次是哪里调用的它,因此会返回到正确的地方
  • return语句的作用:停止函数的执行,并在需要返回值得时候返回一个值
  • 函数返回的值可以赋值给某个变量,可以再传递给某个函数,也可以丢弃
  • 对于没有返回值的函数
    • void 函数名(参数表)
    • 不能使用带值的return(可以没有return)
    • 调用的时候不能做返回值的赋值

再次强调单一出口

//单一出口,推荐
int max(int a, int b)
{
    int ret;
    if ( a > b ) {
        ret = a;
    } else {
        ret = b;
    }
    return ret;
}

/********************************************************/

//非单一出口,不推荐
int max(int a, int b)
{
    if ( a > b ) {
        return = a;
    } else {
        return = b;
    }
}

lesson 3 函数的参数与变量

函数原型

  • 函数头,以分号 “;” 结尾,即构成了函数原型
  • 函数原型的作用是告诉编译器该函数的名称、参数(数量及类型)和 返回类型
  • 旧版标准习惯把函数原型写在调用它的函数里面
  • 现在则习惯于把函数原型写在调用它的函数前面
  • 函数原型可以不写参数的名字

参数传递

  • 如果函数有参数,那么调用函数时就必须传递给他数量、类型正确的值
  • 可以传递给函数的值是表达式的结果,包括:
    int a = 5, b = 6, c;
    c = max(10, 12);	//字面量
    c = max(a, b);		//变量
    c = max(c, 23);		//字面量和变量
    c = max(max(23, 45), a);	//函数的返回值
    c = max(23 + 45, b);		//计算表达式
    
  • 调用函数时给的值和参数类型不是必须严格匹配是C语言传统上最大的漏洞,但编译器总是悄悄把类型转换好,但精度可能会丢失,因此最终的结果可能并非是自己想要的
  • C语言调用函数时只能传值给函数
  • 关于传值
    • 每个函数都有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系
    • 对于函数参数表中的参数,叫做“形式参数”,调用函数给的值,叫做“实际参数”
    • 由于初学者容易误会“实际参数”就是实际在函数中进行计算的参数,错认为调用函数的时候把变量而不是值传进去了,因此 “形参”与“实参”的叫法在当下已经不再被建议

本地变量/局部变量/自动变量

  • 函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,就是函数这次运行所独有的,故称作本地变量
  • 定义在函数内部的变量就是本地变量
  • 参数也是本地变量
  • 变量的生存期和作用域
    • 生存期:变量从出现到消亡
    • 作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)
    • 对于本地变量,这两个概念是统一的,大括号内——块
  • 本地变量的规则
    • 定义在块内
      • 可以是函数的块内
      • 可以是语句的块内
      • 可以是随便写的一个大括号内
    • 程序运行进入这个块内之前,其中的变量不存在,离开这个块,其中的变量就消失了
    • 在块外定义的变量在块内仍然有效
    • 块里面定义了和外面同名的变量则在该块内使用该变量会掩盖外面那一个的值
    • 不能在一个块内定义同名的变量
    • 本地变量不会被默认初始化
    • 参数在进入函数的时候会被初始化
  • 平白无故加一对大括号往往是用于输出调试

注意点

  • 关于逗号
    • 调用函数时的圆括号里的逗号是标点符号,不是运算符
    • f(a, b) ≠ f(f(a, b)),右边表达式里的逗号才是运算符
  • C语言不允许嵌套定义,可以在一个函数里声明另一个函数,但不能在一个函数里定义另一个函数

小练习

  • 以下哪句不是正确的函数原型?
    • A. int f();
    • B. int f(int i);
    • C. int f(int);
    • D. int f() {}
    • 正确答案:D
  • 以下哪个函数的定义是错误的?
    • A. void f() {}
    • B. void f(int i) { return i+1; }
    • C. void f(int i) {}
    • D. int f() { return 0; }
    • 正确答案:B
  • 对于不返回值而且只有一个int类型的参数的函数,以下哪些函数原型是正确的?
    • A. void f(int x);
    • B. void f();
    • C. void f(int);
    • D. void f(x);
    • 正确答案:A、B、C

lesson4 二维数组

  • int a[3][5]; 表示一个三行五列的矩阵
  • 二维数组的遍历需要两层循环
  • a[i, j] 中的逗号是一个运算符,和a[i][j]不是一回事
  • 可以进行定位(C99 ONLY)

二维数组的初始化

int a[][5] = {
	{0, 1, 2, 3, 4,},
	{2, 3, 4, 5, 6,},
}//在大括号里给了元素,则为数组的集成初始化
  • 列数必须给出,行数可以由编译器来数
  • 每行一个{},逗号分隔
  • 最后的逗号可以存在,此乃古老传统,装逼用
  • 如果省略则表示补零

Week 7 数组运算

lesson 1 数组运算

集成初始化时的定位

int a[10] = {
	[1] = 2, 5, [4] = 3, 6,
}
//将数组输出:0 2 5 0 3 6 0 0 0 0
  • 用 [n] 在初始化数据中给出定位
  • 没有定位的数据接在前面的位置
  • 其他位置的值补零
  • 也可以不给出数组大小,让编译器算
  • 特别适合初始数据稀疏的数组
  • C99 ONLY

数组的大小

  • sizeof 给出整个数组所占据的内容的大小,单位时字节
  • sizeof(a) / sizeof(a[0]) 这句话表示该数组的元素个数,好处是一旦需要修改数组中初始的数据,不需要修改遍历的代码
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,};
int i;
printf("%lu\n", sizeof(a));
printf("%lu\n", sizeof(a[0]));
for ( i = 0; i < sizeof(a) / sizeof(a[0]); i++ ) {
	printf("%d\t", a[i]);
}
printf("\n");

运行结果

数组的赋值

  • 不能拿一个数组去给另一个数组赋值
  • 要把一个数组的所有元素赋给另一个数组,只能采用遍历

实例1:找数字

//在一组给定的程序中,判断某个数据是否存在
/**
找出key在数组中的位置
@param key 要寻找的数字
@param a 要寻找的数组
@param length 数组a的长度
@return 如果找到,返回其在a中的位置;反之返回-1
*/
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 main(void)
{
	int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,};
    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不存在\n", x);
    }
    return 0;
}

运行结果
当数组作为函数的参数时

  • 必须要再用另一个参数来传入数组的大小
  • 不能在 [] 中给出数组的大小
  • 不能再利用 sizeof 来计算数组的元素个数

实例2:求素数

//求素数
int isPrime(int x, int knowPrimes[], int numberOfKnowPrimes)
{
    int ret = 1;
    int i;
    for ( i = 0; i < numberOfKnowPrimes; i++ ) {
        if ( x % knowPrimes[i] == 0 ) {
            ret = 0;
            break;
        }
    }
    return ret;
}

int main(void)
{
    const int number = 10;
    int prime[number] = {2};
    int count = 1;
    int i = 3;
    {
        int i;
        printf("\t\t");
        for ( i = 0; i < number; i++ ) {
            printf("%d\t", i);
        }
        printf("\n");
    }
    while ( count < number ) {
        if ( isPrime ( i, prime, count ) ) {
            prime[count++] = i;
        }
        {
            printf("i=%d \tcnt=%d\t", i, count);//输出的是外面的i
            int i;
            for ( i = 0; i < number; i++ ) {
                printf("%d\t", prime[i]);
            }
            printf("\n");
        }
        i++;
    }
    for ( i = 0; i < number; i++ ) {
        printf("%d", prime[i]);
        if ( (i + 5) % 5 ) printf("\t");
        else printf("\n");
    }
    return 0;
}

程序设计入门—C语言(MOOC—翁恺)_第15张图片

实例3:构造 n 以内(不含n)的素数表

  • 算法:
  1. 令 x 为2
  2. 将 2x、3x、4x 直至 ax
  3. 令 x 为下一个没有被标记为非素数的数,重复2;直到所有的数都已经尝试完毕
  • 伪码:
  1. 开辟 prime[n] ,初始化其所有元素为 1,prime[x] 为 1 表示 x 是素数
  2. 令 x=2
  3. 如果 x 是素数,则对于 ( i = 2; x * i < n; i++ ) 令 prime[i * x] = 0
  4. 令 x++,如果 x < n,重复 3,否则结束
  • 源码:
    int main()
    {
        const int maxNumber = 25;
        int isPrime[maxNumber];
        int i;
        int x;
        for ( i = 0; i < maxNumber; i++ ) {
            isPrime[i] = 1;
        }
        for ( x = 2; x < maxNumber; x++ ) {
        	for ( i = 2; i * x < maxNumber; i++ ) {
                isPrime[i * x] = 0;
        	}
        }
        for ( i = 2; i < maxNumber; i++ ) {
            if ( isPrime[i] ) {
                printf("%d\t", i);
            }
        }
        printf("\n");
    	return 0;
    }
    
    运行结果

lesson 2 搜索/查找

  • 对于一个元素已经按照从小到大(或从大到小)排好序的数组
  • 在一个数组中找到某个数的位置(或确认是否存在)
  • 基本方法:遍历
  • 数组元素遍历函数
    int search(int key, int a[], int len)
    {
        int ret = -1;
        for ( int i = 0; i < len; i++ ) {
            if( key == a[i] ) {
                ret = a[i];
                break;
            }
        }
        return ret;		//再次强调:单一出口原则
    }
    //一个变量 只能 承担一个功能,表达一个含义
    //len 的值由 sizeof(a) / sizeof(a[0]) 传入
    
  • 遍历属于线性搜索,线性搜索木的效率
  • 二分法查找数组元素函数
    int search(int key, int a[], int len)
    {
        int left = 0;
        int right = len - 1;
        while ( ) {
            int mid = (left + right) / 2;
            if ( a[min] == k ) {
                ret = mid;
            } else if ( a[min] > k) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return ret;
    }
    

lesson 3 排序初步

  • 选择排序算法(非标准版)
    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;
    }
    
    int main()
    {
        int a[] = {2,45,6,51,4,25,99,52,32,65,18};
        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;
    }
    
  • 快排算法(来源百度百科)
    void sort(int *a, int left, int right)
    {
        if(left >= right)/*如果左边索引大于或者等于右边的索引就代表已经整理完成一个组了*/
        {
            return ;
        }
        int i = left;
        int j = right;
        int key = a[left];
         
        while(i < j)/*控制在当组内寻找一遍*/
        {
            while(i < j && key <= a[j])
            /*而寻找结束的条件就是,1,找到一个小于或者大于key的数(大于或小于取决于你想升序还是降序)
            2,没有符合条件1的,并且i与j的大小没有反转*/ 
            {
                j--;/*向前寻找*/
            }
             
            a[i] = a[j];
            /*找到一个这样的数后就把它赋给前面的被拿走的i的值
            (如果第一次循环且key是a[left],那么就是给key)*/
             
            while(i < j && key >= a[i])
            /*这是i在当组内向前寻找,同上,不过注意与key的大小关系停止循环和上面相反,
            因为排序思想是把数往两边扔,所以左右两边的数大小与key的关系相反*/
            {
                i++;
            }
             
            a[j] = a[i];
        }
         
        a[i] = key;/*当在当组内找完一遍以后就把中间数key回归*/
        sort(a, left, i - 1);/*最后用同样的方式对分出来的左边的小组进行同上的做法*/
        sort(a, i + 1, right);/*用同样的方式对分出来的右边的小组进行同上的做法*/
        /*当然最后可能会出现很多分左右,直到每一组的i = j 为止*/
    }
    

Week 8 指针与字符串

lesson 1 指针

  • sizeof 运算符
    • 给出某个类型或变量在内存中所占据的字节数
    • sizeof(int);
    • sizeof(i);
  • 运算符 &
    • 获得变量的地址,操作数必须是变量
    • 地址的空间大小是否与变量相同取决于编译器
    • 不能对没有地址的东西取地址
      • &(a+b) ×
      • &(a++) ×
      • &(++a) ×
  • 数组名称的地址等同于数组第0个元素的地址

指针

  • 一个指针类型的变量就是保存地址的变量
  •   int i;			
      int* p = &i;	
      //p是一个指针,*表示 p 是一个指向int类型的指针,把 i 的地址交给了p
      int* p, q;		
      int *p, q;		
      //表达的意思同上一行,都表示p是一个指向int类型变量的指针,q是一个int类型的变量
      
      //*p 是一个int,所以 p 是一个指针
    
  • C语言没有 *p 这种类型

指针变量

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

作为参数的指针

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

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

传入函数的数组

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

数组参数

以下四种函数原型是等价的:

  1. int sum(int *ar, int n);
  2. int sum(int *, int );
  3. int sum(int ar[], int n);
  4. int sum(int [], int );

数组变量

  • 数组变量是特殊的地址
    • 数组变量本身就表达地址
      • int a[10]; int *p = a; 无需用&取地址
    • 但数组的单元表达的是变量,需要用&取地址
      • i == &a[0];
  • [] 运算符可以对数组做,也可以对指针做
    • a[0] <==> p[0]
    • *对于一个数组,p 就相当于 p[0]
    •   int i = 2;
        int *p = &i;
        printf("*p=%d\n", *p);
        printf("p[0]=%d\n", p[0]);	//把i当作一个数组
        //两者的表示的数值是一样的,都是2
      
  • *运算符可以对数组做,也可以对指针做
  • 数组变量是 const 类型的指针,所以不能相互赋值
    • int a[] <==> int * const a =…

lesson 2 字符类型

char类型

  • char 可以定义字符型,也可以定义整型
  • char 定义字符学习要用单引号括起来
  • 输入输出里用 %c 来表示字符型
  • scanf 的空格:
    • 如果是 %d %d,有空格,则scanf读取第一个数值给第一个 %d 后要把之后的空格也读取了(忽略空格),当读到下一个非空格的数值时再读给下一个 %d
    • 如果是 %d%d,无空格,则scanf读取第一个数值给第一个 %d 后紧接着的下一个数值(包括空格)将读给下一个 %d

大小写转换

  • ‘a’ - ‘A’ 可得到这两个字符在ASCII表里的距离,于是
    • 字符变量 + ‘a’ - ‘A’ 可以把大写字母变成小写字母
    • 字符变量 + ‘A’ - ‘a’ 可以把小写字母变成大写字母

lesson 3 字符串

字符串的概念与性质

  • 字符数组和字符串不一样
    程序设计入门—C语言(MOOC—翁恺)_第16张图片程序设计入门—C语言(MOOC—翁恺)_第17张图片
  • 字符串本质上还是字符数组,但却以 ‘\0’ 结尾
  • 以0(整数0)结尾的一串字符,0 和 ‘\0’ 相同,但和 '0’不同
  • 计算字符串长度的时候不会包含这个0,0只标志字符串的结束却不是字符串的一部分
  • 字符串以数组的形式存在,以数组或指针的形式访问(更多是指针)
  • 头文件 string.h 里有很多处理字符串的函数

字符串常量

    • char *str = "hello";
    • char word[] = "hello";
    • char line[10] = "hello"
  • 以上的 “hello” 会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有一个0
  • 两个相邻的字符串常量会被自动连接起来

字符串变量

  • char* s = "hello world";
  • s 是一个指针,初始化为指向一个字符串常量
  • 由于这个常量所处的内存空间,实际上 s 是 const char* s,不过由于历史的原因,编译器接受不带 const 的写法
  • 但是试图对 s 所指的字符串做写入会导致严重的后果
  • “hello world” 所在的内存空间是被操作系统保护的只读空间
  • 如果要对字符串进行修改,则应使用字符串数组的形式
    • char s[] = "hello world";

字符串用指针还是数组

    • char *str = "hello";
    • char word[] = "hello";
  • 如果要构造一个字符串,要进行修改——用数组
  • 如果要处理一个字符串,不需要修改——用指针

clar* 是字符串吗

  • 字符串可以表达为 char* 的形式
  • char* 不一定是字符串
    • 本意是指向字符的指针,可能指向的字符的数组(就像 int* 一样)
    • 只有它所指的字符数组有结尾的0,才能说它说指的是字符串

小练习

  • 1、已知:

    int a[] = {5, 15, 34, 54, 14, 2, 52, 72};
    int *p = &a[5];
    

    则:p[-2]的值是?

    • A. 编译出错,因为数组下标越界了
    • B. 运行出错,因为数组下标越界了
    • C. 54
    • D. 2
    • 正确答案:C
  • 2、已知:

    int a[] = {0};
    int *p = a;
    

    则:以下哪些表达式的结果为真?

    • A. p == a[0]
    • B. p == &a[0]
    • C. *p == a[0]
    • D. p[0] == a[0]
    • 正确答案:B、C、D
  • 3、已知:

    int a[] = {5, 15, 34, 54, 14, 2, 52, 72};
    int *p = &a[1];
    

    则:p[2]的值是?

    • 正确答案:54

lesson 4 字符串计算

字符串赋值

  •   char *t = "title";
      char *s;
      s = t;
    
  • 以上代码并没有产生新的字符串,只是让指针 s 指向了 t 所指的字符串,对 s 的任何操作就是对 c 做的

字符串输入输出

  •   char string[8];
      scanf("%s", string);
      printf("%s", string);
    
  • scanf 读入一个单词,直到空格、tab 或回车为止
  •   void f(void)
      {
      	char word[8];		//7个有效字符,最后一个为0
      	char word2[8];		//7个有效字符,最后一个为0
      	scanf("%7s", word);		//最多输入7个字符
      	scanf("%7s", word2);	//最多输入7个字符
      	printf("%s##%s##\n", word, word2);
      }
    
  • scanf 是不安全的,因为不知道要读入的内容的长度

字符串输入常见错误

程序设计入门—C语言(MOOC—翁恺)_第18张图片

空字符串

程序设计入门—C语言(MOOC—翁恺)_第19张图片

字符串函数(string.h)

  • strlen
    • size_t strlen(const char *s);
    • 返回 s 的字符串长度(不包括结尾的0,不同于 sizeof)
  • strcmp
    • int strcmp(const char *s1, const char *s2);
    • 比较两个字符串
      • 如果返回值 < 0,则表示 s1 小于 s2。
      • 如果返回值 > 0,则表示 s2 小于 s1。
      • 如果返回值 = 0,则表示 s1 等于 s2。
  • strcpy
    • char *strcpy(char *restrict dest, const char *restrict src)
    • dest——指向用于存储复制内容的目标数组
    • src——要复制的字符串
    • restrict——表达dest和src不重叠
  • 等等等等

你可能感兴趣的:(C语言)