C 环境设置
如果您想要设置 C 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C 编译器。
- 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。C 程序的源文件通常使用扩展名 .c
- 写在源文件中的源代码是人类可读的源。它需要"编译",转为机器语言,这样 CPU 可以按给定指令执行程序。
- C 语言编译器用于把源代码编译成最终的可执行程序。最常用的免费可用的编译器是 GNU 的 C/C++ 编译器
C 程序结构
C 程序主要包括以下部分:
预处理器指令;函数;变量;语句 & 表达式;注释
#include <stdio.h>
int main()
{
/* 我的第一个 C 程序 */
printf("Hello, World! \n");
return 0;
}
编译 & 执行 C 程序
- 打开一个文本编辑器,添加上述代码。
- 保存文件为 hello.c。
- 打开命令提示符,进入到保存文件所在的目录。
- 键入 gcc hello.c,输入回车,编译代码。
- 如果代码中没有错误,命令提示符会跳到下一行,并生成 a.out 可执行文件。
- 现在,键入./ a.out 来执行程序。 您可以看到屏幕上显示 “Hello World”。
C 基本语法
C 的令牌(Token)
C 程序由各种令牌组成,令牌可以是关键字、标识符、常量、字符串值,或者是一个符号。例如,下面的 C 语句包括五个令牌:
printf("Hello, World! \n");
注意:
- 在 C 程序中,分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。
- C 语言有两种注释方式:
// 单行注释
/* 单行注释 */
/*
多行注释
多行注释
多行注释
*/
- C 标识符是用来标识变量、函数,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。
- C 标识符内不允许出现标点字符,比如 @、$ 和 %。C 是区分大小写的编程语言。因此,在 C 中,Manpower 和 manpower 是两个不同的标识符。
- 关键字 https://www.runoob.com/cprogramming/c-basic-syntax.html
- C 中的空格
只包含空格的行,被称为空白行,可能带有注释,C 编译器会完全忽略它。
在 C 中,空格用于描述空白符、制表符、换行符和注释。空格分隔语句的各个部分,让编译器能识别语句中的某个元素(比如 int)在哪里结束,下一个元素在哪里开始。因此,在下面的语句中:
int age;
在这里,int 和 age 之间必须至少有一个空格字符(通常是一个空白符),这样编译器才能够区分它们。另一方面,在下面的语句中:
fruit = apples + oranges; // 获取水果的总数
fruit 和 =,或者 = 和 apples 之间的空格字符不是必需的,但是为了增强可读性,您可以根据需要适当增加一些空格
数据类型
常用基本数据类型占用空间(64位机器为例)
char : 1个字节
int :4个字节
float:4个字节
double:8个字节
基本类型书写
- 整数
a,默认为10进制 ,10 ,20。
b,以0开头为8进制,045,021。
c.,以0b开头为2进制,0b11101101。
d,以0x开头为16进制,0x21458adf。 - 小数
单精度常量:2.3f 。
双精度常量:2.3,默认为双精度。 - 字符型常量
用英文单引号括起来,只保存一个字符’a’、‘b’ 、‘*’ ,还有转义字符 ‘\n’ 、‘\t’。 - 字符串常量
用英文的双引号引起来 可以保存多个字符:“abc”。
注:表达式 sizeof(type) 得到对象或类型的存储字节大小。
变量
变量其实只不过是程序可操作的存储区的名称。C 中每个变量都有特定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。
变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下划线开头。大写字母和小写字母是不同的,
C 语言也允许定义各种其他类型的变量,比如枚举、指针、数组、结构、共用体等等,
基本变量类型
- C 中的变量定义
变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。变量定义指定一个数据类型,并包含了该类型的一个或多个变量的列表
int i, j, k;
char c, ch;
float f, salary;
double d;
//变量可以在声明的时候被初始化(指定一个初始值)。初始化器由一个等号,后跟一个常量表达式组成
extern int d = 3, f = 5; // d 和 f 的声明与初始化
int d = 3, f = 5; // 定义并初始化 d 和 f
byte z = 22; // 定义并初始化 z
char x = 'x'; // 变量 x 的值为 'x'
- 变量声明
变量声明向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。
变量的声明有两种情况:
1、一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间。
2、另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a 其中变量 a 可以在别的文件中定义的。
extern int i; //声明,不是定义
int i; //声明,也是定义
除非有extern关键字,否则都是变量的定义。
#include <stdio.h>
// 函数外定义变量 x 和 y
int x;
int y;
int addtwonum(){
// 函数内声明变量 x 和 y 为外部变量
extern int x;
extern int y;
// 给外部变量(全局变量)x 和 y 赋值
x = 1;
y = 2;
return x+y;
}
int main(){
int result;
// 调用函数 addtwonum
result = addtwonum();
printf("result 为: %d",result);
return 0;
}
如果需要在一个源文件中引用另外一个源文件中定义的变量,我们只需在引用的文件中将变量加上 extern 关键字的声明即可。
#include <stdio.h>
/*外部变量声明*/
extern int x ;
extern int y ;
int addtwonum()
{
return x+y;
}
#include <stdio.h>
/*定义两个全局变量*/
int x=1;
int y=2;
int addtwonum();
int main(void)
{
int result;
result = addtwonum();
printf("result 为: %d\n",result);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
$ gcc addtwonum.c test.c -o main
$ ./main
result 为: 3
常量
常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。
常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。
常量就像是常规的变量,只不过常量的值在定义后不能进行修改。
//0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制
85 /* 十进制 */
0213 /* 八进制 */
0x4b /* 十六进制 */
30 /* 整数 */
//带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)
//后缀可以是大写,也可以是小写,U 和 L 的顺序任意
30u /* 无符号整数 */
30l /* 长整数 */
30ul /* 无符号长整数 *
3.14159 /* 合法的 */
314159E-5L /* 合法的 */
510E /* 非法的:不完整的指数 */
210f /* 非法的:没有小数或指数 */
.e55 /* 非法的:缺少整数或分数 */
//字符常量是括在单引号中,例如,'x' 可以存储在 char 类型的简单变量中。
//常用转义字符
\\ // \ 字符
\' //' 字符
\" // " 字符
\? // ? 字符
\f //换页符
\n //换行符
\t //水平制表符
//字符串常量
//字符串字面值或常量是括在双引号 "" 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。
定义常量
在 C 中,有两种简单的定义常量的方式:
使用 #define 预处理器。
使用 const 关键字。
#define identifier value
const int LENGTH = 10; //const int LENGTH;是错误的
C 存储类
存储类定义 C 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。
下面列出 C 程序中可用的存储类:
auto
register
static
extern
后续补充
C 运算符
运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言内置了丰富的运算符,并提供了以下类型的运算符:
算术运算符
关系运算符
逻辑运算符
位运算符
赋值运算符
杂项运算符
后续补充
判断+循环
补充说明Switch
#include <stdio.h>
int main ()
{
/* 局部变量定义 */
char grade = 'B';
switch(grade) //switch 语句中的 expression 是一个常量表达式,必须是一个整型或枚举类型
{
case 'A' : //每个 case 后跟一个要比较的值和一个冒号。
printf("很棒!\n" );
break; //当被测试的变量等于 case 中的常量时,case 后跟的语句将被执行,直到遇到 break 语句为止。
case 'B' :
case 'C' :
printf("做得好\n" );
break;
case 'D' :
printf("您通过了\n" );
break;
case 'F' :
printf("最好再试一下\n" );
break; //如果 case 语句不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止
default : // break 语句不是必需的。
printf("无效的成绩\n" );
}
printf("您的成绩是 %c\n", grade );
return 0;
}
- ? : 运算符(三元运算符)
(num%2==0)?printf("偶数"):printf("奇数");
函数
函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。
您可以把代码划分到不同的函数中。但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
C 标准库提供了大量的程序可以调用的内置函数。例如,函数 strcat() 用来连接两个字符串,函数 memcpy() 用来复制内存到另一个位置。
函数还有很多叫法,比如方法、子例程或程序,等等。
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
- 函数声明
函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。
int max(int num1, int num2);
//在函数声明中,参数的名称并不重要,只有参数的类型是必需的
int max(int, int);
//在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。
//在这种情况下,您应该在调用函数的 文件 顶部 声明函数
函数参数
/* 函数定义 */
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */
return;
}
传值调用
向函数传递参数的传值调用方法,把参数的实际值复制给函数的形式参数。
在这种情况下,修改函数内的形式参数不会影响实际参数。
/* 调用函数来交换值 */
swap(a, b);
引用方式调用函数
通过引用传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问。
/* 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
swap(&a, &b);
C 作用域规则
任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。
C 语言中有三个地方可以声明变量:
- 在函数或块内部的局部变量:它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的
- 在所有函数外部的全局变量:通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量
注:在程序中,局部变量和全局变量的名称可以相同,但是在函数内,如果两个名字相同,会使用局部变量值,全局变量不会被使用。下面是一个实例: - 在形式参数的函数参数定义中:函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用
注:全局变量与局部变量在内存中的区别:
全局变量保存在内存的全局存储区中,占用静态的存储单元;
局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。
数组
说明
//声明
double balance[10];
//初始化数组
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
//大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目。
//如果您省略掉了数组的大小,数组的大小则为初始化时元素的个数。
//为数组中某个元素赋值的实例:
balance[4] = 50.0;
//访问
double salary = balance[9];
传参和返回参数
如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。
void myFunction(int *param) //形式参数是一个指针
void myFunction(int param[10]) //形式参数是一个已定义大小的数组:
void myFunction(int param[]) //形式参数是一个未定义大小的数组
{}
C 语言不允许返回一个完整的数组作为函数的参数。但是,您可以通过指定不带索引的数组名来返回一个指向数组的指针
如果您想要从函数返回一个一维数组,您必须声明一个返回指针的函数,如下:
int * myFunction()
{.}
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* 要生成和返回随机数的函数 */
int * getRandom( ){
static int r[10];
int i;
/* 设置种子 */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i) {
r[i] = rand();
printf( "r[%d] = %d\n", i, r[i]);
}
return r;
}
/* 要调用上面定义函数的主函数 */
int main (){
/* 一个指向整数的指针 */
int *p;
int i;
p = getRandom();
for ( i = 0; i < 10; i++ ){
printf( "*(p + %d) : %d\n", i, *(p + i));
}
return 0;
}
指向数组的指针
数组名是一个指向数组中第一个元素的常量指针
double *p;
double balance[10];
//balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址
p = balance;
//使用数组名作为常量指针是合法的,反之亦然。因此,*(balance + 4) 是一种访问 balance[4] 数据的合法方式。
//一旦您把第一个元素的地址存储在 p 中,您就可以使用 *p、*(p+1)、*(p+2) 等来访问数组元素
枚举
enum(枚举)
枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
//注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。
//我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
enum DAY day;
int main()
{
enum DAY day;
day = WED;
printf("%d",day); //3
return 0;
}
用处较少,忽略
指针
正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。
声明
指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
//星号是用来指定一个变量是指针
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
使用
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值
#include <stdio.h>
int main ()
{
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
//int var[] = {10, 100, 200};
//int *ptr;
/* 指针中第一个元素的地址 */
//如果是数组,可以省略符号:ptr = var;
/* 指针中最后一个元素的地址 */
//ptr = &var[MAX-1];
printf("var 变量的地址: %p\n", &var );
/* 在指针变量中存储的地址 */
printf("ip 变量存储的地址: %p\n", ip );
/* 使用指针访问值 */
printf("*ip 变量的值: %d\n", *ip );
return 0;
}
/* 如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。 */
int *ptr = NULL;
//如果指针包含空值(零值),则假定它不指向任何东西
if(ptr) /* 如果 p 非空,则完成 */
指针运算
- C 指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。可以对指针进行四种算术运算:++、–、+、-。
ptr++
指针的每一次递增,它其实会指向下一个元素的存储单元。
指针的每一次递减,它都会指向前一个元素的存储单元。
指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节。
- 指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
指针数组
数组存储指向 int 或 char 或其他数据类型的指针。下面是一个指向整数的指针数组的声明:
int *ptr[MAX];
//把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr[MAX];
for ( i = 0; i < MAX; i++) {
ptr[i] = &var[i]; /* 赋值为整数的地址 */
}
return 0;
}
指向指针的指针
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int **var;
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符
传递指针给函数
#include <stdio.h>
/* 函数声明 */
double getAverage(int *arr, int size);
int main (){
/* 带有 5 个元素的整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ;
/* 输出返回值 */
printf("Average value is: %f\n", avg );
return 0;
}
double getAverage(int *arr, int size){
int i, sum = 0;
double avg;
for (i = 0; i < size; ++i) {
sum += arr[i];
}
avg = (double)sum / size;
return avg;
}
从函数返回指针
C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
/* 要生成和返回随机数的函数 */
int * getRandom( )
{
static int r[10];
int i;
/* 设置种子 */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i)
{
r[i] = rand();
printf("%d\n", r[i] );
}
return r;
}
/* 要调用上面定义函数的主函数 */
int main ()
{
/* 一个指向整数的指针 */
int *p;
int i;
p = getRandom();
for ( i = 0; i < 10; i++ )
{
printf("*(p + [%d]) : %d\n", i, *(p + i) );
}
return 0;
}
字符串
在 C 语言中,字符串实际上是使用 null 字符 \0 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
下面的声明和初始化创建了一个 RUNOOB 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 RUNOOB 的字符数多一个。
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
依据数组初始化规则,您可以把上面的语句写成以下语句:
char site[] = "RUNOOB";
其实,您不需要把 null 字符放在字符串常量的末尾。C 编译器会在初始化数组时,自动把 \0 放在字符串的末尾
C 中有大量操作字符串的函数:
1 strcpy(s1, s2);
复制字符串 s2 到字符串 s1。
2 strcat(s1, s2);
连接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1);
返回字符串 s1 的长度。
4 strcmp(s1, s2);
如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5 strchr(s1, ch);
返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2);
返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
结构体
结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性:
Title
Author
Subject
Book ID
struct Books //结构体标签Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book; //结构变量book
结构体标签 和 结构变量 必须二选一出现一个
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
扩展:结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
//此结构体的声明包含了其他的结构体
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};
//此结构体的声明包含了指向自己类型的指针
struct NODE
{
char string[100];
struct NODE *next_node;
};
初始化
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
访问结构成员
//为了访问结构的成员,我们使用成员访问运算符(.)
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* 输出 Book1 信息 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);
结构体作为参数
void printBook( struct Books book )
{
printf( "Book title : %s\n", book.title);
printf( "Book author : %s\n", book.author);
printf( "Book subject : %s\n", book.subject);
printf( "Book book_id : %d\n", book.book_id);
}
指向结构的指针
您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
//现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,
//请把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
//为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:
struct_pointer->title;
共用体
共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
union Data
{
int i;
float f;
char str[20];
} data;
位域
如果程序的结构中包含多个开关量,只有 TRUE/FALSE 变量,如下:
struct
{
unsigned int widthValidated;
unsigned int heightValidated;
} status;
这种结构需要 8 字节的内存空间,但在实际上,在每个变量中,我们只存储 0 或 1。在这种情况下,C 语言提供了一种更好的利用内存空间的方式。如果您在结构内使用这样的变量,您可以定义变量的宽度来告诉编译器,您将只使用这些字节。例如,上面的结构可以重写成:
struct
{
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status;
现在,上面的结构中,status 变量将占用 4 个字节的内存空间,但是只有 2 位被用来存储值。如果您用了 32 个变量,每一个变量宽度为 1 位,那么 status 结构将使用 4 个字节,但只要您再多用一个变量,如果使用了 33 个变量,那么它将分配内存的下一段来存储第 33 个变量,这个时候就开始使用 8 个字节。让我们看看下面的实例来理解这个概念
typedf
typedef 关键字,您可以使用它来为类型取一个新的名字
typedef unsigned char BYTE;
//为单字节数字定义了一个术语 BYTE
typedef vs #define
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
#include <stdio.h>
#define TRUE 1
#define FALSE 0
int main( )
{
printf( "TRUE 的值: %d\n", TRUE);
printf( "FALSE 的值: %d\n", FALSE);
return 0;
}
输入 & 输出
scanf() 函数用于从标准输入(键盘)读取并格式化, printf() 函数发送格式化输出到标准输出(屏幕)。
#include <stdio.h> // 执行 printf() 函数需要该库
int main()
{
int testInteger = 5;
printf("Number = %d", testInteger);
return 0; //表示退出程序
}
int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。
int main( )
{
int c;
printf( "Enter a value :");
c = getchar( );
printf( "\nYou entered: ");
putchar( c );
printf( "\n");
return 0;
}
$./a.out
Enter a value :runoob
You entered: r
char *gets(char *s) 函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。
int put在这里插入代码片
s(const char *s) 函数把字符串 s 和一个尾随的换行符写入到 stdout。
#include <stdio.h>
int main( )
{
char str[100];
printf( "Enter a value :");
gets( str );
printf( "\nYou entered: ");
puts( str );
return 0;
}
$./a.out
Enter a value :runoob
You entered: runoob
int scanf(const char *format, …) 函数从标准输入流 stdin 读取输入,并根据提供的 format 来浏览输入。
int printf(const char *format, …) 函数把输出写入到标准输出流 stdout ,并根据提供的格式产生输出。
format 可以是一个简单的常量字符串,但是您可以分别指定 %s、%d、%c、%f 等来输出或读取字符串、整数、字符或浮点数。还有许多其他可用的格式选项,可以根据需要使用。如需了解完整的细节,可以查看这些函数的参考手册。现在让我们通过下面这个简单的实例来加深理解
#include <stdio.h>
int main( ) {
char str[100];
int i;
printf( "Enter a value :");
scanf("%s %d", str, &i);
printf( "\nYou entered: %s %d ", str, i);
printf("\n");
return 0;
}
$./a.out
Enter a value :runoob 123
You entered: runoob 123
预处理器
C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。
所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:
#define MAX_ARRAY_LENGTH 20
//这个指令告诉 CPP 把所有的 MAX_ARRAY_LENGTH 替换为 20。使用 #define 定义常量来增强可读性。
#include <stdio.h>
#include "myheader.h"
//这些指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。
//下一行告诉 CPP 从本地目录中获取 myheader.h,并添加内容到当前的源文件中。
#undef FILE_SIZE
#define FILE_SIZE 42
//这个指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42`
#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif
//这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。#ifdef DEBUG
/* Your debugging statements here */
#endif
//这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,
//如果您向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用。
//它定义了 DEBUG,您可以在编译期间随时开启或关闭调试。
头文件
头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。
在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它。前面我们已经看过 stdio.h 头文件,它是编译器自带的头文件。
引用头文件相当于复制头文件的内容,但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别在程序是由多个源文件组成的时候。
A simple practice in C 或 C++ 程序中,建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件。
#include <file>
//这种形式用于引用系统头文件。它在系统目录的标准列表中搜索名为 file 的文件。
#include "file"
//这种形式用于引用用户头文件。它在包含当前文件的目录中搜索名为 file 的文件
只引用一次头文件
如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
/* 这种结构就是通常所说的包装器 #ifndef。当再次引用头文件时,条件为假,
//因为 HEADER_FILE 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它*/
强制类型转换
类型转换可以是隐式的,由编译器自动执行,
也可以是显式的,通过使用强制类型转换运算符来指定。
在编程时,有需要类型转换的时候都用上强制类型转换运算符,是一种良好的编程习惯。
错误处理
C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。在发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误。您可以在 errno.h 头文件中找到各种各样的错误代码。
所以,C 程序员可以通过检查返回值,然后根据返回值决定采取哪种适当的动作。开发人员应该在程序初始化时,把 errno 设置为 0,这是一种良好的编程习惯。0 值表示程序中没有错误。
- C 语言提供了 perror() 和 strerror() 函数来显示与 errno 相关的文本消息。
perror() 函数显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式。
strerror() 函数,返回一个指针,指针指向当前 errno 值的文本表示形式。
/**模拟一种错误情况,尝试打开一个不存在的文件。您可以使用多种方式来输出错误消息*/
#include <stdio.h>
#include <errno.h>
#include <string.h>
extern int errno ;
int main ()
{
FILE * pf;
int errnum;
pf = fopen ("unexist.txt", "rb");
if (pf == NULL)
{
errnum = errno;
fprintf(stderr, "错误号: %d\n", errno);
perror("通过 perror 输出错误");
fprintf(stderr, "打开文件错误: %s\n", strerror( errnum ));
}
else
{
fclose (pf);
}
return 0;
}
/*应该使用 stderr 文件流来输出所有的错误*/
错误号: 2
通过 perror 输出错误: No such file or directory
打开文件错误: No such file or directory
- 被零除的错误
在进行除法运算时,如果不检查除数是否为零,则会导致一个运行时错误。
为了避免这种情况发生,下面的代码在进行除法运算前会先检查除数是否为零:
#include <stdio.h>
#include <stdlib.h>
main()
{
int dividend = 20;
int divisor = 0;
int quotient;
if( divisor == 0){
fprintf(stderr, "除数为 0 退出运行...\n"); //结果
exit(-1);
}
quotient = dividend / divisor;
fprintf(stderr, "quotient 变量的值为 : %d\n", quotient );
exit(0);
}
//结果
//除数为 0 退出运行...
程序退出状态
通常情况下,程序成功执行完一个操作正常退出的时候会带有值 EXIT_SUCCESS。在这里,EXIT_SUCCESS 是宏,它被定义为 0。
如果程序中存在一种错误情况,当您退出程序时,会带有状态值 EXIT_FAILURE,被定义为 -1。所以,上面的程序可以写成:
#include <stdio.h>
#include <stdlib.h>
main()
{
int dividend = 20;
int divisor = 5;
int quotient;
if( divisor == 0){
fprintf(stderr, "除数为 0 退出运行...\n");
exit(EXIT_FAILURE);
}
quotient = dividend / divisor;
fprintf(stderr, "quotient 变量的值为: %d\n", quotient );
exit(EXIT_SUCCESS);
}
//结果
//quotient 变量的值为 : 4
递归
#include <stdio.h>
double factorial(unsigned int i){
if(i <= 1){
return 1;
}
return i * factorial(i - 1);
}
int main(){
int i = 15;
printf("%d 的阶乘为 %f\n", i, factorial(i));
return 0;
}
可变参数
希望函数带有可变数量的参数,而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定义。
//函数 func() 最后一个参数写成省略号,即三个点号(...),
//省略号之前的那个参数是 int,代表了要传递的可变参数的总数
//需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏
int func(int, ... ) {
.
.
.
}
int main()
{
func(2, 2, 3);
func(3, 2, 3, 4);
}
使用方法:
- 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
- 使用 int 参数和 va_start,宏来初始化 va_list 变量为一个参数列表;宏 va_start 是在 stdarg.h 头文件中定义的。
- 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。 使用宏 va_end 来清理赋予 va_list 变量的内存。
- 使用宏 va_end 来清理赋予 va_list 变量的内存。
#include <stdio.h>
#include <stdarg.h>
double average(int num,...){
va_list valist;
double sum = 0.0;
int i;
/* 为 num 个参数初始化 valist */
va_start(valist, num);
/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++){
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);
return sum/num;
}
int main(){
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
内存管理
C 中的动态内存管理。C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char name[100];
char *description;
strcpy(name, "Zara Ali");
//预先不知道需要存储的文本长度,例如您想存储有关一个主题的详细描述。
//在这里,我们需要定义一个指针,该指针指向未定义所需内存大小的字符,
//后续再根据需求来分配内存
/* 动态分配内存 */
description = (char *)malloc( 200 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student in class 10th");
}
printf("Name = %s\n", name );
printf("Description: %s\n", description );
//重新分配内存
/* 假设您想要存储更大的描述信息 */
description = (char *) realloc( description, 100 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcat( description, "She is in class 10th");
}
printf("Name = %s\n", name );
printf("Description: %s\n", description );
}
内容总结于https://www.runoob.com/cprogramming/c-tutorial.html菜鸟教程
2021-11-08