P 100 变量作用域基本规则 2023/1/9
一、基本介绍
概念:所谓变量作用域(Scope),就是指变量的有效范围。
- 函数内部声明/定义的局部变量,作用域仅限于函数内部。
#include<stdio.h>
void sayHello() {char name[] = "tom"; // 这里name就是局部变量,作用域仅限于在sayHello函数中!!!printf("hello %s \n", name);
}
void main() {sayHello();//这里我们不能使用到sayHello的name变量printf("name= %s", name); //这里将提示,没有定义name
}
- 函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用局部变量(编译器使用就近原则)
#include<stdio.h>
int n = 20; // 函数外部定义的变量,就是全局变量(作用域在整个程序中都可以使用)// 函数的形参,会被视为f10的局部变量
// 说明:当局部变量与全局变量同名时,以局部变量为准(就近原则)
void f10(int n){printf("\nn=%d",n);
}void main() {f10(10); // 当函数外定义了同名变量,依然还是输出10getchar();
}
- 在一个代码块,比如 for / if中 的局部变量,那么这个变量的的作用域就在该代码块。
void main() {int i = 0;for (i = 0; i < 10; i++){int k = 90; //k 的作用域在for代码块中printf("i=%d k = %d\n", i, k);}printf("k=%d", k); // 这里不能使用for中定义的k变量,因为k的作用域范围只在for循环中。getchar();
}
- 在所有函数外部定义的变量叫全局变量,作用域在整个程序有效。(通常将全局变量放在头文件中)
myFun.h // 在myFun.h中定义了全局变量,使用只需要引入头文件就行,但是只能引入一次,引入多次会重复定义。double money = 1.1; //定义了全局变量
hello.c
#include "myFun.h"; // 这里引入即可
P 101 变量初始化注意事项 2023/1/9
一、初始化注意事项
- 局部变量,系统不会对其默认初始化,必须对局部变量初始化后才能使用,否则,程序运行后可能会异常退出。
#include<stdio.h>
void main(){// 局部变量,系统不会对其初始化。// 必须对局部变量初始化后才能使用,否则,程序运行后可能会异常退出。int i;printf("%d",i);
}
- 全局变量,系统会自动对其初始化,如下所示:
数据类型 | 初始化默认值 |
---|---|
int | 0 |
char | ‘\0’ |
float | 0.0 |
double | 0.0 |
pointer 指针 | NULL |
#include<stdio.h>
int a;
float f;
double d1; // 以上定义为全局变量void main() { // 全局变量会初始化,默认输出下面的值printf("\na=%d f=%.2f d1=%.2f", a, f, d1); // a = 0,f = 0.00,d1 = 0.00getchar();
}
- 正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果,因为未初始化的变量会导致一些在内存位置中已经可用的垃圾值。(异常退出可能就是指向了一些垃圾值)
P 102 作用域细节和内存布局图 2023/1/10
一、注意事项和细节
- 全局变量(Global Variable)保存在内存的全局存储区中,占用静态的存储单元,它的作用域默认是整个程序,也就是所有的代码文件,包括源文件(.c文件)和头文件(.h文件)。【c程序内存布局图!!!----非常重要】
-
局部变量(Local Variable)保存在栈中,函数被调用时才动态地为变量分配存储单元,它的作用域仅限于函数内部。【内存布局分析 】
-
C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量。
int a;
float f;
double d1; // 以上定义为全局变量void f20(){int num = 90; // 函数,定义就是局部变量printf("a=%d",a); //在局部里面去使用这个a,相当于是从小的范围里面像大的范围里面去找// 但是外面是不可以使用局部变量里面的num
}
- 在同一个作用域,变量名不能重复,在不同的作用域,变量名可以重复,使用时编译器采用就近原则.
void f20(){ int num = 90; // 函数,定义就是局部变量int num = 89; // 在同一个作用域里面是不能重复定义printf("a=%d",a);
}void f30(){ // 我在f30里面在定义一个num,在不同作用域是不会冲突的int num = 100;
}
- 由{ }包围的代码块也拥有独立的作用域(例如使用for、if前面讲解过)。
void f30(){int num = 100;if(1){int num = 200; // 这里再次定义同样不会报错// 对于if,外面的就相当于全局变量,里面的是局部变量,里面的是小范围,外面的是大范围,采用就近原则。}
}
P 103 作用域课堂练习题 2023/1/15
一、题目练习
题目一:下面的代码输出什么内容?
#include<stdio.h>double price = 200.0; //全局变量
void test01() {printf("%.2f \n", price); //函数里面没有定义, 就是 全局变量 : 200.0
}
void test02() { //编译器采用就近原则price = 250.0; // 如果把 这句话 改成 double price = 250.0 , 输出有什么变化?
// double price = 250.0;
// 定义之后就是局部变量了,跟全局变量没有关系。printf("%.2f \n", price); // 250.0
}
void main() {//main函数printf("main price=%.2f \n", price); // 200.00test01(); // 200.0test02(); // 250.0 , 将 全局price 改成 250test01();// 250.0;如果修改double price = 250 后,会输出200getchar();
}
题目二:下面代码输出什么?
#include<stdio.h>
int n = 10; //全局变量
void func1(){int n = 20; //局部变量printf("func1 n: %d\n", n);
}void func2(int n){ // 这里的n是形参nprintf("func2 n: %d\n", n);
}void func3(){printf("func3 n: %d\n", n); // 这个n就是全局的变量,因为自己里面没有定义也没有接收形参
}int main(){int n = 30; //局部变量(main函数)func1(); // 输出20func2(n); // 这里表示主函数中的n传到func2,输出30func3(); // 输出10//代码块由{}包围{int n = 40; //局部变量printf("block n: %d\n", n); // 这里的n就是代码块里面的n,输40}printf("main n: %d\n", n); // 这里找主方法里面的n,输出30getchar();return 0;
}
P 104 static关键字修饰变量 2023/1/15
一、基本介绍
介绍:static关键字在c语言中比较常用,使用恰当能够大大提高程序的模块化特性,有利于扩展和维护。
二、局部变量使用static修饰
-
局部变量被 static 修饰后,我们称为静态局部变量。
-
对应静态局部变量在声明时未赋初值,编译器也会把它初始化为0。
#include<stdio.h>
void main(){ static int n ; // n静态局部变量,默认初始化值为0printf("\n n=%d",n); // 会输出0getchar();
- 静态局部变量存储于进程的静态存储区(前面布局图)(全局性质),只会被初始一次,即使函数返回,它的值也会保持不变 [案例+图解]。
- 普通变量每次都会初始化,n在栈区,调用一次就会产生新的n;而静态局部变量,放在静态存储区,全局性质空间。
void fn_static(void) {static int n = 10; // 静态局部变量printf("\nstatic n=%d\n", n);n++;printf("\nn++=%d\n", n);
}
int main(void) {fn_static(); // 这里输出10,11fn_static(); //输出11,12;因为只会被初始化一次,依照上一次的值进行++;所以输出11,12
}
三、全局变量使用static修饰
- 普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量),静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。[案例]
// file01中定义普通全局变量
int num = 10; // 普通全局变量
static int num2 = 20; // 静态全局变量,只能在本文件中使用,而不能在其他文件使用。// file02中引入输出
#include<stdio.h>// 在一个文件中,使用另外一个文件的全局变量,使用exter引入即可
extern int num;
extern int num2; // 但是这里虽然引入了,但是num2是静态全局变量,依然是不可以使用的
// int num2; 但是再定义一个num2两者是不会冲突的
void main(){printf("num = %d",num);printf("num2 = %d",num2);getchar();
}
- 定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。
P 105 静态函数讲解和使用 2023/1/16
一、函数使用static修饰
- 函数的使用方式与全局变量类似,在函数的返回类型前加上static,就是静态函数
static void fun2(void) {//静态函数,它只能在本文件中使用printf("hello from fun2.\n");
}
- 非静态函数可以在另一个文件中通过extern 引用 【案例】
extern void fun1(void);
- 静态函数只能在声明它的文件中可见,其他文件不能引用该函数[案例]
// file03 文件中
#include <stdio.h>
void fun1(void) {//普通函数(非静态函数)printf("hello from fun1.\n");
}
static void fun2(void) {//静态函数,它只能在本文件中使用printf("hello from fun2.\n");
}// file04 文件中
#include<stdio.h>extern void fun1(void);
extern void fun2(void); // 这里引入静态函数,但是下面调用是会报错的void main(void){fun1();fun2(); // 这里会报错无法解析外部符号报错getchar();
}
- 不同的文件可以使用相同名字的静态函数,互不影响[案例]
void fun2(){ // 在上面file03文件中有fun2,但是是静态函数,可以使用同名函数
//.....
};void fun1(){ // 但是非静态函数时不能同名的,这里则会报错
//.....
};
P 106 常用的字符串函数 2023/1/16
一、字符串中常用的系统函数
**说明:**字符串(即 字符数组)在我们程序开发中,使用的是非常多的,常用的函数需要同学们掌握[带看手册或者官方编程指南 头文件 <string.h>]:
- 得到字符串的长度
size_t strlen(const char *str)
计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。
#include<stdio.h>
#include<string.h> // 头文件中声明了字符串相关的系统函数void main(){char src[50] = "abc", dest[50]; // 定义了两个字符数组(字符串),大小为50char * str = "abcdff";printf("str.len=%d", strlen(str)); // 统计字符串的大小,会返回6。
- 拷贝字符串
char *strcpy(char *dest, const char *src)
把 src 所指向的字符串复制到 dest。
// 表示将hello拷贝到src// 注意:拷贝字符串会将原来的内容覆盖,所以会输出hellostrcpy(src, "hello ");printf("\n src = %s",src);strcpy(dest, "尚硅谷"); // 同样是拷贝
- 连接字符串
char *strcat(char *dest, const char *src)
把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
// strcat 是将src字符串内容连接或者追加到dest,但不会覆盖原有的内容,而是连接!!strcat(dest, src); // 输出 hello尚硅谷printf("最终的目标字符串: |%s|", dest); getchar();
}
P 107 常用的日期时间函数 2023/1/16
一、日期和时间相关函数
说明:在编程中,程序员会经常使用到日期相关的函数,比如:统计某段代码执行花费的时间等等。头文件是 <time.h>(学完结构体在温习)
- 获取当前时间
char *ctime(const time_t *timer)
返回一个表示当地时间的字符串,当地时间是基于参数timer。
#include <stdio.h>
#include <time.h> // 该头文件中,声明了日期和时间相关的函数
int main () {time_t curtime; // time_h是一个结构体类型time(&curtime); // time() 完成初始化printf("当前时间 = %s", ctime(&curtime)); // ctime 可以接受time_t这个类型,并且返回一个当前时间// 返回一个表示当地时间的字符串,当地时间是基于参数timer。getchar();return(0);
}
- 编写一段代码来统计 函数test 执行的时间
double difftime(time_t time1, time_t time2)
返回 time1 和 time2 之间相差的秒数 (time1-time2)。
#include <stdio.h>
#include <time.h>
void test() { // 运行test函数,看看执行花费的时间(一般用于做一些函数的时间测试)int i = 0;int sum = 0;int j = 0;for(i = 0; i < 77777777;i++) {sum = 0;for (j = 0; j< 10;j++) {sum += j;}}
}int main(){// 先得到执行test前的时间time_t start_t,end_t;double diff_t; // 存放时间差printf("程序启动…\n");time(&start_t); // 初始化得到当前时间test(); // 执行test// 再得到执行test后的时间time(&end_t); // 得到当前时间diff_t = difftime(end_t,start_t); // 时间差就是按秒计算,ent_t - start_t// 然后得到两个时间差就是耗用的时间printf("执行test()函数 耗用了%.2f 秒",diff_t);getchar();
return 0;
}
P 108 常用的数学函数 2023/1/16
一、数学相关函数
说明:math.h 头文件定义了各种数学函数和一个宏。在这个库中所有可用的功能都带有一个 double 类型的参数,且都返回 double 类型的结果
-
double exp(double x)
返回 e 的 x 次幂的值。 -
double log(double x)
返回 x 的自然对数(基数为 e 的对数) -
double pow(double x, double y)
返回 x 的 y 次幂。 -
double sqrt(double x)
返回 x 的平方根。 -
double fabs(double x)
返回 x 的绝对值。
#include <stdio.h>
#include <math.h>
void main (){double d1 = pow(2.0,3.0); // 2的3次幂 输出8double d2 = sqrt(4.0); // 4的开方为2double d3 = log(4.5); // 以e为底的自然对数为1.504double d4 = fabs(-3.0); // -3的绝对值double d5 = exp(3.0); // 以常数e为底数,3.0为指数的值为20.09printf("d1=%.2f\n", d1);printf("d2=%.2f\n", d2);printf("d3=%.2f\n", d3);printf("d4=%.2f\n", d4);printf("d5=%.2f\n", d5);getchar();
}
P 109 基本数据类型和字符串互转 2023/1/16
一、基本介绍
介绍:在程序开发中,我们经常需要将基本数据类型转成字符串类型(即 char数组 )。或者将字符串类型转成基本数据类型。
二、sprintf函数的用法
-
sprintf和平时我们常用的printf函数的功能很相似。sprintf函数打印到字符串中,而printf函数打印输出到屏幕上。sprintf函数在我们完成其他数据类型转换成字符串类型的操作中应用广泛。
-
该函数包含在stdio.h的头文件中
- 基本类型转字符串类型
#include<stdio.h>void main() {char str1[20]; //字符数组,即字符串char str2[20];char str3[20];int a=20984,b=48090;double d=14.309948;//说明:// 1.sprintf是一个系统函数,可以将结果存放在字符串中// 2. 格式化的结果,会存放到str1中sprintf(str1,"%d %d",a,b); // 存放到str1中sprintf(str2, "%.2f", d); sprintf(str3, "%8.2f", d); // %8.2f,格式化后总共有8位,小数点后占用2位,不够用空格占位printf("str1=%s str2=%s str3=%s", str1, str2, str3);getchar();
}
- 字符串类型转基本数据类型
语法:通过<stdlib.h>的函数调用 atoi atof 即可
#include<stdio.h>
#include<stdlib.h>void main() {char str[10] = "123456";char str2[10] = "12.67423";char str3[2] = "a";char str4[4] = "111";// 说明:// 1. atoi(str) 将str转成整数int num1 = atoi(str);short s1 = atoi(str4);// 说明:// 1.atoi(str2);将str2转成小数double d = atof(str2);// 说明:// 1. str3[0] 表示获取到str3这个字符串(数组)的第一个元素 'a' char c = str3[0];printf("num1=%d d=%f c=%c s1=%d", num1, d, c, s1);getchar();
}
三、注意事项
- 在将char 数组 类型转成 基本数据类型时,要确保能够转成有效的数据,比如 我们可以把 “123” , 转成一个整数,但是不能把 “hello” 转成一个整数。
- 如果格式不正确,会默认转成 0 或者 0.0 [案例演示]
char str[10] = "hello";int num1 = atoi(str); // 强制转换会变成0
P 110 函数课堂练习题 2023/1/18
一、题目练习
题目一:函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金子塔。 【课后练习】 => 将原来的代码封装到函数中即可
#include<stdio.h>
// 题目:函数可以没有返回值案例,编写一个函数,从终端输入一个整数(层数)打印出对应的金子塔。
// 层数 toltalLevel是通过形参传入
void Pyramid(int totalLevel){int a,b,c; // 定义a表示层数,b表示* 的个数,c来表示空格for(a = 1;a <= totalLevel;a++){// 输出空格for(c=1; c<=totalLevel - a;c++){printf(" ");}for(b = 1;b<= 2 * a - 1;b++){ // 打印半个金字塔将b<=a即可,a为行数,每层的*与层数相等。// 打印整个金字塔就将改为b<=2*a-1,每一层的*的个数的规律 if(b == 1 || b == 2*a-1|| a == totalLevel){ // a = 5,表示最后一层,a表示层,所以使用最后一层printf("*"); // 打印空心金字塔,在此处考虑输出空格}else{printf(" ");}}printf("\n");}getchar();
}void main(){int a = 5;printf("请输入金字塔的层数");scanf("%d",&a);// 调用函数Pyramid(a);getchar();
}
题目二:编写一个函数,从终端输入一个整数(1—9),打印出对应的乘法表。
#include<stdio.h>
void MultiplicationTable(int Num){int i,j;for(i = 1; i <=Num; i++){for(j = 1;j<=i; j++){printf("%d * %d = %d ",j,i,i*j);}printf("\n");}getchar();
}void main(){int Num = 10; // 也可以改成手动输入MultiplicationTable(Num);
}
题目三:编写函数,对给定的一个二维数组(3×3)转置,这个题讲数组的时候再完成
题目四:定义函数,实现求两个double数字的最大值,并返回。
#include<stdio.h>
double getMax(double d1,double d2 ){return d1 > d2 ?d1:d2; // 使用一个三元运算
}
void main(){printf("最大值=%.2f",getMax(1.1,7.8));getchar();
}
题目五: 定义函数,求出三个int 类型数的和,并返回。
#include<stdio.h>
double Sum(int a,int b,int c){int res = 0;res = a + b + c;return res;
// return a+b+c; 建议直接返回
}
void main(){printf("和=%.2f",Sum(1,2,2));getchar();
}
题目六:定义函数,求出一个int类型数组中的最大值的索引 [数组学习后完成]
题目七:定义函数,可以接受一个double类型数组,使用冒泡法对其排序 [数组学习后完成]