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); // 当函数外定义了同名变量,依然还是输出10
getchar();
}
- 在一个代码块,比如 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.00
getchar();
}
- 正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果,因为未初始化的变量会导致一些在内存位置中已经可用的垃圾值。(异常退出可能就是指向了一些垃圾值)
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.00
test01(); // 200.0
test02(); // 250.0 , 将 全局price 改成 250
test01();// 250.0;如果修改double price = 250 后,会输出200
getchar();
}
题目二:下面代码输出什么?
#include<stdio.h>
int n = 10; //全局变量
void func1(){
int n = 20; //局部变量
printf("func1 n: %d\n", n);
}
void func2(int n){ // 这里的n是形参n
printf("func2 n: %d\n", n);
}
void func3(){
printf("func3 n: %d\n", n); // 这个n就是全局的变量,因为自己里面没有定义也没有接收形参
}
int main(){
int n = 30; //局部变量(main函数)
func1(); // 输出20
func2(n); // 这里表示主函数中的n传到func2,输出30
func3(); // 输出10
//代码块由{}包围
{
int n = 40; //局部变量
printf("block n: %d\n", n); // 这里的n就是代码块里面的n,输40
}
printf("main n: %d\n", n); // 这里找主方法里面的n,输出30
getchar();
return 0;
}
P 104 static关键字修饰变量 2023/1/15
一、基本介绍
介绍:static关键字在c语言中比较常用,使用恰当能够大大提高程序的模块化特性,有利于扩展和维护。
二、局部变量使用static修饰
-
局部变量被 static 修饰后,我们称为静态局部变量。
-
对应静态局部变量在声明时未赋初值,编译器也会把它初始化为0。
#include<stdio.h>
void main(){
static int n ; // n静态局部变量,默认初始化值为0
printf("\n n=%d",n); // 会输出0
getchar();
- 静态局部变量存储于进程的静态存储区(前面布局图)(全局性质),只会被初始一次,即使函数返回,它的值也会保持不变 [案例+图解]。
- 普通变量每次都会初始化,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,11
fn_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]; // 定义了两个字符数组(字符串),大小为50
char * str = "abcdff";
printf("str.len=%d", strlen(str)); // 统计字符串的大小,会返回6。
- 拷贝字符串
char *strcpy(char *dest, const char *src)
把 src 所指向的字符串复制到 dest。
// 表示将hello拷贝到src
// 注意:拷贝字符串会将原来的内容覆盖,所以会输出hello
strcpy(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次幂 输出8
double d2 = sqrt(4.0); // 4的开方为2
double d3 = log(4.5); // 以e为底的自然对数为1.504
double d4 = fabs(-3.0); // -3的绝对值
double d5 = exp(3.0); // 以常数e为底数,3.0为指数的值为20.09
printf("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类型数组,使用冒泡法对其排序 [数组学习后完成]