C语言学习笔记
函数
子函数在主函数之后,须在主函数开头部分声明子函数。
定义
无参函数
类型标识符 函数名() {
函数体
}
类型标识符 函数名(void) {
函数体
}
- void表示空类型,无函数值(返回值)
- 函数体包括'声明部分'和'语句部分'
有参函数
类型标识符 函数名(形式参数表列) {
函数体
}
类型标识符 函数名(类型标识符 形式参数, 类型标识符 形式参数, .....) {
函数体
}
例如:
int max(int x,int y) {
int z; // 声明部分
z = x>y ? x : y; // 执行部分
return z;
}
空函数
类型标识符 函数名() {
}
C语言中,若未说明函数的类型,则系统默认该函数的类型是int型。
调用
格式
格式: 函数名(实参表列)
- 函数调用语句,针对无返回值的函数
printf("hello");
- 函数表达式,针对有返回值的函数
c = 2 * max(1,2);
- 函数参数,作为另一个函数的实参
m = max(1,max(2,3));
printf("max:%d",max(5,11));
形参与实参
在有参函数中,定义函数时函数名后面括号里的是形参
,在主函数中调用时,被调用函数名后面括号里的是实参
。实参可以是常量,变量,表达式。实参要求必须是确定的值。
数组作为函数参数
数组元素作为函数实参,其用法与变量相同,向形参传递数组元素的值。
数组名也可以作为实参和形参,传递的是数组第一个元素的地址
数组元素作为函数实参
可做实参,不可做形参,因为形参是在函数被调用处临时分配存储单元的,不可能为一个数组元素单独分配存储空间。是 ”值传递“ 方式,数据传输方向是从实参传递到形参,单向传递。
一维数组名作为函数参数
用数组元素作为实参时,向形参彻底的是数组元素的值。数组名作为函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。
当用数组名作为函数的参数时,如果形参数组中的元素值发生改变时 实参元素的值也发生改变。
-
用数组名作为函数的参数时,应在主调用函数和被调用函数分别定义数组
-
实参数组与形参数组应类型一致
-
在定义函数时,指定数组大小时不起任何作用,因为C语言编译器不检查形参数组大小,只是将实参数组的首元素的地址传递给形参数组名
-
形参数组可以不知道大小
void foo(int a[]){ ......}
编译系统会把形参数组处理为指针变量,例如把float a[]转化为float *a,二者是等价的
多维数组名作为函数参数
可省略第一维大小
void foo(int a[][10]){
......}
嵌套调用
C语言的函数定义是互相平行的、独立。在定义函数时,一个函数内不能在定义另一个函数,即不能嵌套定义,
但可以嵌套调用函数。
递归调用
直接或间接地调用该函数本身,称为函数的递归调用。
int foo(int x) {
int y,z;
z=foo(y);
return 2*z;
}
声明
来源
函数的来源有两种:1. 用户自定义的函数,2. 库函数。
-
被调用的函数必须是已定义的函数。
-
使用库函数,需要在本文件开头用
#include
指令将所需要的信息添加到本文件中。#include <math.h> .h是头文件所用的后缀,表示头文件
-
如果用户自定义的函数在主函数后面定义,需要在主函数开头声明该函数。以便编译器能够正确识别到。因为编译器是自上向下进行的。
-
如果已在文件的开头(在所有函数之前),对本文件中所调用的函数进行声明,则在个函数中不必对其所调用的函数再做声明。
int max(int,int); int add(int,int); int main() { ......} // 在main函数中就无需对max和add函数进行声明 int max(int x,int y){ ......} int add(int x,int y){ ......}
格式
一般形式:
- 函数类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...)
- 函数类型 函数名(参数类型1, 参数类型2, ...)
例如:
float add(float,float);
float add(float x,float y);
返回值
-
函数的返回值通过函数中的return语句获得
-
在定义函数时必须指定函数的类型
-
在定义函数指定的函数类型一般应该和return语句中的表达式一致,即函数类型觉得返回值类型
-
对于不带回值的函数,应当定义函数为 “void类型”
变量的作用域
在函数开头定义的变量,只在该函数中使用。引申到变量的作用域问题,每个变量都有一个作用域。
在函数内定义的变量是局部变量;在函数外定义的变量是全局变量。
定义变量的三种情况:
-
在函数开头的位置定义
-
在函数内的复合语句内定义
-
在函数外部定义
局部变量
-
主函数定义的变量只在主函数中有效
-
不同函数可以使用相同名的变量,他们代表不同的对象,互不干扰
-
形式参数也是局部变量
-
复合语句中定义的变量只在复合语句中有效
全局变量
为了便于区分全局变量和局部变量,有一个习惯将全局变量第一个字母大写
建议不在必要时不要使用全局变量,理由如下:
-
全局变量在程序的全部执行中占用内存
-
使函数的通用性降低
-
使用全局变量过多,会降低程序的清晰性
举例
// 变量的作用域
#include<stdio.h>
int G = 123;
int add(int x, int y) {
printf("add中打印g:%d\n", G);
return x + y;
}
char M = 'M'; // 编译器自上向下执行语句,虽然M和G都是全局作用域,但m的作用域比g小,不包含add函数
int main() {
int a = 0;
int sum=0;
for (int i = 0; i < 10 ;i++) {
int c=10;
sum = sum + add(a, i);
}
G = 321;
//G = G + i + c; // 未定义标识符 "i" 和 "c"
//printf("G:%d; a:%d; sum:%d; c:%d; i:%d\n",G,a,sum,c,i); // c和i无效
printf("g:%d; a:%d; sum:%d;\n", G, a, sum);
}
若全局变量与局部变量同盟,分析结果
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:4996) // 禁止显示strcpy等不安全函数的警告
#pragma warning(disable:6031) // 禁止显示未经检查的返回值警告
#include<stdio.h>
int a = 3, b = 5;
int main() {
int max(int a, int b);
int a = 8;
printf("main里的a:%d; b:%d\n", a, b); // a:8(局部变量) b:5(全局变量)
printf("max=%d\n", max(a, b));
return 0;
}
int max(int a, int b) {
int c;
printf("max里的a:%d; b:%d\n", a, b); // a,b为局部变量
c = a > b ? a : b;
return c;
}
// max=8
变量的存储方式和生存期
存储方法
变量的存储方式有两种:
-
静态存储方式
-
动态存储方式
静态存储方式:指程序运行期间由系统分配固定的存储空间的方式。
动态存储方式:指在程序运行期间根据需要进行的动态分配存储空间的方式。
常量的存储方式
取决于常量的类型和存储类别。一般来说,C语言中的常量存储方式有以下特点:
字面常量(Literal Constants):字符串常量和数值常量(包括整型、浮点型和字符型)通常存储在程序的静态存储区(Static Memory),也被称为常量存储区(Constant Pool)。这个区域在程序运行期间始终存在,不会随函数调用结束而消失。字符串常量尤为特殊,它们在内存中以只读形式存储,不可修改。
const修饰的变量(Const Variables):当使用
const
关键字定义一个常量变量时,虽然它在概念上被视为常量,但在内存中仍可能分配存储空间,且存储在静态存储区。一旦初始化,其值就不能再改变。编译时常量(Compile-Time Constants):对于整型和指针类型的编译时常量(如用
#define
预处理器定义的宏或者枚举常量),它们在编译阶段就被替换成了具体的值,不会在运行时占用内存空间。简而言之,在C语言中,大多数常量在内存中的存储是静态的,并且在程序的整个生命周期内保持不变。字符串常量具有额外的只读属性,防止它们在运行时被修改。而用
const
修饰的变量虽然名义上是常量,但在内存中仍有其专属的存储空间。
内存中供用户使用的存储空间,可分为三部分:
-
程序区
-
静态存储区
-
动态存储器
数据分别存放在静态存储区和动态存储区中,全局变量
全部
存放在静态存储区中,在整个程序执行期间
一直存在,程序开始
时分配存储空间,程序结束
时释放。
一般静态存储区存放全局变量,常量等。
在动态存储区中存放下列数据:
-
函数形式参数。在调用函数时给形参分配存储空间。
-
函数中定义没有用关键字static声明的变量,即自动变量。
-
函数调用时的现场包含和返回地址等。
存储类别
每一个变量和函数有两个属性:数据类型和数据的存储类别
C语言的存储类别包括4种:auto、static、register、extern;前三种用于局部变量,extern用于全局变量
局部变量的存储类别
-
auto—自动局部变量
使用关键字auto作为自动变量的存储列表声明。
函数中的局部变量,如果没有专门用static(静态)声明存储类别,都是动态分配存储空间的。
关键字auto可省略,不写auto则隐含指定为 ”自动存储类别“int foo(int a){ auto int b=2; return a+b; }
-
static—静态局部变量
使用关键字static声明。
函数调用结束后不会消失,而继续保留原值,即其占用的存储空间不释放,在下一次调用该函数时,该变量的值是上一次函数调用结束的值。
非必要情况下不要使用静态局部变量,会多占用内存空间。
建议定义静态局部变量时,同时初始化值,以免每次调用时重新赋值。// 考虑静态变量的 #include<stdio.h> int main() { int foo(int); int a = 2, i; for ( i = 0; i < 3; i++<