C语言基础预备
前言
接上文内容,本文仍然是C语言基础内容的预备知识。
11、 常见关键字
上面面是常见的关键字,先介绍以下几个关键字:
11.1 关键字 typedef
typedef 顾名思义是类型定义,这里应该理解为类型重命名,例如,将unsigned int 重命名为uint_32, 所以uint_32也是一个类型名,观察num1和num2,这两个变量的类型是一样的。
//将unsigned int 重命名为uint_32, 所以uint_32也是一个类型名
typedef unsigned int uint_32;
int main()
{
//观察num1和num2,这两个变量的类型是一样的
unsigned int num1 = 0;
uint_32 num2 = 0;
return 0;
}
11.2 关键字static
在C语言中,static是用来修饰变量和函数的,有3个用法:
- 修饰局部变量-称为静态局部变量
- 修饰全局变量-称为静态全局变量
- 修饰函数-称为静态函数
11.2.1 修饰局部变量
static修饰局部变量,延长作用周期,下面举例说明:
//代码1
#include <stdio.h>
void test()
{
int a = 0;
a++;
printf("%d ", a);//输出10个1
}
int main()
{
int i = 0;
for(i=0; i<10; i++)
{
test();
}
return 0;
}
//代码2
void test()
{
//static修饰局部变量
static int a = 0;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
for(i=0; i<10; i++)
{
test();//输出1-10
}
return 0;
}
对比代码1和代码2的的结果,理解static修饰局部变量的意义。
代码1打印结果: 1 1 1 1 1 1 1 1 1 1
代码2打印结果: 1 2 3 4 5 6 7 8 9 10
代码1中, 局部变量a在test()函数内,其保存在栈区了。函数运行结束时,i的值就没有了,存放变量i的内存空间释放了。等下一次调用函数test()时,变量a又要重新初始化,赋值为0。
其作用范围: 就在test()函数内作用域,出来函数数值销毁
作用周期:,函数开始调用到函数调用结束,生命周期结束
代码2中, static修饰局部变量a,其保存在内存中的静态区了。在test()函数内,函数运行结束时,a的值仍然存在,存放变量a的内存空间没有释放,。等下一次调用函数test()时,静态局部变量a不需要重新赋值为0,这句代码已经失效了,变量a会保留上一次的,并在此基础上执行语句。
其作用范围: 出了test()函数的作用域,数值仍然存在;
**作用周期:**整个工程结束时,生命周期才结束。
结论:
static修饰局部变量改变了变量的生命周期
让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。
11.2.2 修饰全局变量
代码1正常,代码2在编译的时候会出现连接性错误。
结论:
一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。
//代码1
//add.c
int g_val = 2018;//函数定义在add.c的源文件中
//test.c
int main()//主函数在test.c的源文件中
{
printf("%d\n", g_val);
return 0;
}
//代码2
//add.c
static int g_val = 2018;//函数定义在add.c的源文件中
//test.c
int main()//主函数在test.c的源文件中
{
printf("%d\n", g_val);
return 0;
}
//代码3
//extern 是用来声明外部符号的
extern int global;//在add源文件中定义的全局变量可以在test中使用
int main()
{
printf("%d\n", global);
return 0;
}
说明:一个全局变量是具有外部链接属性的,被static修饰后,外部连接属性变成了内部链接属性,只能在自己所在的源文件内使用,不能被其他源文件使用了。感觉是作用域变小了。
11.2.3 修饰函数
代码1正常,代码2在编译的时候会出现连接性错误.
结论:
一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。
//代码1
//add.c
int Add(int x, int y)
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}
//代码2
//add.c
static int Add(int x, int y)//只能在本源文件中使用
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}
//代码3
//将在其他源文件定义的函数,经过声明以后可以在本源文件中直接使用
extern int add(int x, int y);
int main()
{
int num1 = 9;
int num2 = 5;
int sum = 0;
sum = add(num1, num2);
printf("%d\n", sum);
return 0;
}
说明:一个函数是具有外部链接属性的,被static修饰后,外部链接属性变成了内部链接属性,只能在自己所在的源文件内使用,不能被其他源文件使用了。感觉是作用域变小了。
12、#define 定义常量和宏
宏定义,名称一般全大写
//define定义标识符常量
#define MAX 1000
#define STR "hello bit"//字符串直接用双引号
//define定义宏
#define ADD(x, y) ((x)+(y))
#include <stdio.h>
int main()
{
int sum = ADD(2, 3);
printf("sum = %d\n", sum);
sum = 10*ADD(2, 3);
printf("sum = %d\n", sum);
return 0;
}
13、指针
13.1 内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址:
变量是创建在内存中的(在内存中分配空间的),每个内存单元都有地址,所以变量也是有地址的。取出变量地址如下:
int main()
{
int num = 10;
#//取出num的地址
//这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)
printf("%p\n", &num);//打印地址,%p是以地址的形式打印
return 0;
}
那地址如何存储,需要定义指针变量,指针就是地址。
内存单元都有编号,这个编号就是地址,我们把这个地址也叫指针。存放地址的变量,即存放指针的变量,称为指针变量。
//内存单元都有编号,这个编号就是地址,我们把这个地址也叫指针
int main()
{
int a = 10;
//%p取地址操作,存放变量a数值的地址
printf("%p\n", &a);
return 0;
}
int num = 10;
int *p;//p为一个整形指针变量
p = #//地址传给指针p,
#include <stdio.h>
int main()
{
int num = 10;
//pa是用来存放地址的变量,所以pa为指针变量
int *p = #
//*是解引用操作,*pa,就是将pa存放的地址所对应的值取出来
//*pa原来对应的值是10,现在重新赋值,就是20了
*p = 20;//重新给地址存放的变量赋值
printf("%d\n", a);
return 0;
}
字符使用指针的例子:
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'q';
printf("%c\n", ch);
return 0;
}
13.2 指针变量的大小
指针变量的大小取决于地址的大小
32位平台下地址是32个bit位(即4个字节)
64位平台下地址是64个bit位(即8个字节)
//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("不同类型变量的大小:\n");
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
printf("不同类型的指针变量的大小:\n");
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(float*));
printf("%d\n", sizeof(double*));
return 0;
}
下面这个例子中,一个字符的地址占4个字节,整形数组也是4个字节,两个输出结果都是4。
指针大小就是固定的4个字节。地址的大小和地址存放的数值类型没有任何关系
int main()
{
char ch = 'w';
char* pc = &ch;
printf("%d\n", sizeof(ch));
printf("%d\n", sizeof(pc));
int a = 19;
int* pa = &a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(pa));
//两个输出结果都是4,指针大小就是固定的4个字节
//大小和地址存放的数值类型没有任何关系
//32位平台就是x86, 指针大小是4个字节
//64位平台就是x64, 指针大小是4个字节
return 0;
}
14、 结构体
操作符 -> 是结构成员访问操作符,使用方法如下:
结构体指针->结构体成员
操作符 . 是结构成员访问操作符,使用方法如下:
结构体变量.结构体成员
//结构体变量
struct stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
int main()
{
struct stu s = { "张三",20,"男", "20180101" };
struct stu s2 = { "李四",19,"女", "20180102" };
//printf("name=%s age=%d sex=%s id=%s\n", s.name, s.age, s.sex, s.id);
printf("%s %d %s %s\n", s.name, s.age, s.sex, s.id);
struct stu *ps = &s2;
printf("%s %d %s %s\n", ps->name, ps->age, ps->sex, ps->id);
return 0;
}
使用typedef可以将结构体的名称简写:
stu就是struct stu
typedef struct stu
{
char name[20];
int age;
char sex[5];
char id[20];
}stu;
void Print(struct stu* ps)//必须定义结构体类型的指针变量才能接受地址
{
//printf("%s %d %s %s\n", (*s3).name,(*s3).age, (*s3).sex, (*s3).id);
printf("%s %d %s %s\n", ps->name, ps->age, ps->sex, ps->id);//直接打印即可
//-> 结构成员访问操作符
//结构体指针->结构体成员
};
int main()
{
//结构体的初始化
struct stu s1 = { "张三",20,"男", "20180101" };
struct stu s2 = { "周六",21,"女", "20180103" };
stu s3 = { 0 };//这是使用typedef的效果
//stu就是struct stu
输入结构体数据 name是数组名,就是首地址,age是整数,必须取地址
scanf("%s %d %s %s", s1.name, &(s1.age), s1.sex, s1.id);
//直接打印的
printf("%s %d %s %s\n", s1.name, s1.age, s1.sex, s1.id);
//. 结构成员访问操作符
结构体变量.结构体成员
//使用函数打印
Print(&s2);//传入地址,则函数定义的必须使用指针变量接受参数了
return 0;
}
总结
本文是初始C语言系列的最后一点内容了,目前算是将C语言的大部分内容快速预习了一遍,可以看作是C语言基础内容的预备知识,接下来将进入C语言基础阶段的学习。博客更新内容完全参照B站的学习视频。
之后接着是C语言进阶阶段的学习,最后再学习初级的数据结构与算法内容。这样完整的C语言阶段就结束了。后续也会跟着学习C++了。