初步认识 C语言(下)

简介: 初步认识 C语言(下)

十五、关键字



auto  break  case  char  const  continue  default  do  double 
else  enum  extern  float  for  goto  if  int  long  register  
return  short  signed   sizeof  static  struct  switch  typedef
union  unsigned  void  volatile  while


1. typedef 关键字


typedef 关键字的作用: 可以将类型重命名。


#include <stdio.h>
// 将 unsigned int 类型重命名为 u_int
typedef unsigned int u_int;
int main()
{
  unsigned int a = 0;
  u_int b = 0;
  // a 和 b 的类型是等价的
  return 0;
}


2. register 关键字


register 关键字: register 关键字主要与寄存器有关。


下图是计算机存储数据常见的结构,其中寄存器就是直接集成在 CPU 上的,在过去的时代中,CPU 拿放数据都是与内存进行沟通;随着硬件的发展,CPU 的处理速度越来越快,而现在,由于寄存器的读写速度更快,CPU 可以直接对寄存器进行读写。如果 CPU 想要新的数据,那么内存再逐级往上替换,先替换缓存中的数据,再由缓存中的数据替换掉寄存器中的数据,这样一来,寄存器的数据可以一直保持最新的。


所以,寄存器的作用主要就是提高了 CPU 的处理速度。


500f715c62284ab498b65eb58f6ce5a6.png

鉴于此,C语言 就为我们提供了 register 关键字,可以让我们将变量直接放入寄存器中。但虽说提供了此关键字,但到最后是由编译器决定是否真正地放入寄存器中,因为寄存器的容量非常小,不可能随心所欲。


此外,寄存器变量不能使用 & 取地址,因为涉及到地址,只有内存中才会有。


register int a = 10;


3. static 关键字


(1) static 修饰局部变量


程序清单:


#include <stdio.h>
void test()
{
  int n = 1;
  printf("%d ", ++n);
}
int main()
{
  for (int i = 0; i < 10; i++)
  {
    test();
  }
  return 0;
}


输出结果:


baec9b91acc1405cac68a1624993c756.png


程序清单:


#include <stdio.h>
void test()
{
  static int n = 1; // 将局部变量设置成 static 
  printf("%d ", ++n);
}
int main()
{
  for (int i = 0; i < 10; i++)
  {
    test();
  }
  return 0;
}


输出结果:


7b4932961dbd49af937cbfc2d56a3775.png


总结:


对比上面的两个程序,得出总结:


当 static 关键字修饰一个局部变量的时候,就改变了局部变量的存储类型。原本一个局部变量是存储在栈区的,但被 static 修饰后,它就存储在了静态区。所以,被 static 修饰的局部变量,它的生命周期更长,可以被视为整个工程的生命周期。


94213f39de4c4a5f919bc4a6c6df91b7.png


也就是说,被 static 关键字修饰的局部变量,它出了局部作用域的范围不再被销毁,但它的作用域范围没有被改变。


分析代码: 局部变量 n 的作用域依然是 test 函数的内部,但 n 经初始化,并出了作用域后,不会被销毁。(下面的第三行初始化代码只用了一次)

void test()
{
  static int n = 1; 
  printf("%d ", ++n);
}


(2) static 修饰全局变量


程序清单:


06f5ac63c80f4723b3c0b2c2e85600cd.png


程序清单:


05bd42a1e46240ddb7ebb8672fc9220e.png


总结:


对比上面的两个程序,得出总结:


① 全局变量本身就具有外部链接属性,当一个 .c 文件用到了另一个 .c 文件的局部变量时,只需要在前者加上 extern 关键字声明即可。


② 而 static 修饰全局变量时,就将全局变量原本的外部链接属性变成了内部链接属性,所以后来被 static 修饰的全局变量只能在本 .c 文件中使用。


③ 综上所述,static 修饰全局变量的使用场景就是,在一个工程中,你防止一个 .c 文件篡改了另一个 .c 文件的全局变量数据。但实际上,全局变量用的并不多,因为使用它时,本身就会带来使用变量作用域混乱的问题。


d036cdaa53e141cfb741d17a31de90bf.png


(3) static 修饰函数


static 修饰函数和 static 修饰全局变量的思想差不多。当一个函数被 static 修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用,即使两个源文件属于同一个工程也不行。


4800056e5aed4668a6c5f22ffc47ebb3.png


十六、指针



1. 指针与内存


指针就是地址,有了地址,就能帮助我们快速地找到一块内存空间。


#include <stdio.h>
int main() 
{
  int a = 10;
  int* pa = &a; // 取出 a 的地址赋值给指针变量 pa
  *pa = 20; // *pa == a
  printf("%d\n", a);
  return 0;
}


输出结果:


252472ffdf4548459c8a2bcc7cd32e6c.png


在上面的程序中,


&a 表示取出 int变量 a 的地址 (取出的是 变量a 的第一个字节地址);

*pa 表示解引用 指针变量 pa,*pa 就等价于 a.

如下图所示:(假设虚拟地址空间为 32位)


ebb139b4d4e041689ea92ca7e4a8531a.png


注意事项:


内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。为了能够有效访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。在 C语言中,每创建一个变量就会在底层开辟地址。


① 内存会被划分为小的内存单元,一个内存单元的大小是1个字节。


② 每个内存单元都有编号,这个编号也被称为:地址 / 指针。


③ 地址 / 指针可以存放在一个变量中, 这个变量称为指针变量,指针变量也是一个变量,它也有自己的地址。


④ 通过指针变量中存储的地址,就能找到指针指向的空间。


2. 指针变量的大小


程序清单:


#include <stdio.h>
int main() 
{
  int a = 10;
  char ch = 'a';
  double d = 3.14;
  int* pa = &a;
  char* pc = &ch;
  double* pd = &d;
  printf("%d\n", sizeof(pa));
  printf("%d\n", sizeof(pc));
  printf("%d\n", sizeof(pd));
  return 0;
}


输出结果:


25af19de90914e12a7cddab3c5617d53.png


结论:


指针变量是用来存放地址的。所以,地址的存放需要多大空间,指针变量的大小就应该是多大。


① 32位 机器,支持 32位 虚拟地址空间,其产生的地址就是 32位,所以此时指针变量就需要 32位 的空间存储,即 4字节。

② 64位 机器,支持 64位 虚拟地址空间,其产生的地址就是 64位,所以此时指针变量就需要 64位 的空间存储,即 8字节。


十七、结构体



结构体是 C语言 中的自定义类型,它可以用来描述一个包含多种类型的信息。比方说:一名学生的信息 (名字、年龄、学号)


C语言 的结构体和 Java 中的类差不多,我们刚开始创建的结构体的时候,它就相当于是一块空白的模板,我们可以通过这个模板再引申同种类别的东西。例如,下面的 Student 就相当于一个类型,它的地位与 int、char 差不多,只不过 Student 类型是由我们自己自定义的类型。


程序清单1


#include <stdio.h>
struct Student
{
  char name[20];  // 名字
  int age;    // 年龄
  int studentID;  // 学号
};
int main() 
{
  struct Student student1 = {"Jack", 18, 32};  
  struct Student student2 = {"Bruce", 20, 05};
  printf("%s %d %d\n", student1.name, student1.age, student1.studentID);
  struct Student* ps1 = &student1;
  printf("%s %d %d\n", (*ps1).name, (*ps1).age, (*ps1).studentID);
  printf("%s %d %d\n", ps1->name, ps1->age, ps1->studentID);
  return 0;
}


输出结果:


fff0ec4bdc664028a818f640555af473.png


注意事项:


在访问结构体成员时,可以采用 . 或者 -> ,前者对应结构体变量,后者对应结构体指针变量。


程序清单2


#include <stdio.h>
#include <string.h>
struct Student
{
  char name[20];  // 名字
  int age;    // 年龄
  int studentID;  // 学号
};
int main()
{
  struct Student student1 = { "Jack", 18, 32 };
  printf("%s %d %d\n", student1.name, student1.age, student1.studentID);
  //student1.name = "小红"; // error
  student1.age = 17;
  student1.studentID = 25;
  strcpy(student1.name, "小红");
  printf("%s %d %d\n", student1.name, student1.age, student1.studentID);
  return 0;
}


输出结果:


54c7a0c72fc749e7a3721f0803101649.png

注意事项:


上面的程序中,有一处代码是错误的,因为在 C语言 中,没有字符串类型,它并不像 Java 直接就能修改。由于 name 变量本身是一个字符数组,所以下面的代码使用的其实是数组名,即数组首元素地址。所以从类型的角度来看,字符串 " 小红 " 不能直接赋值给一个指针变量,所以编译器就会直接报错。


student1.name = "小红"; // error


十八、C语言 的内存布局



在我们平时写程序时,创建的变量都会在底层开辟内存。C语言 的内存布局分为三大块:栈区、堆区、静态区。


94213f39de4c4a5f919bc4a6c6df91b7.png


备注:栈区是我们平时使用最多的区域。栈区的使用习惯:先使用高地址处的空间,再使用低地址处的空间。

目录
相关文章
|
编译器 C语言 C++
|
1天前
|
传感器 安全 算法
【C语言】C语言可以做什么?
C语言因其高效、灵活和低级控制能力,被广泛应用于各个领域,从基础设施和科学计算到金融、交通和机器人技术。它在许多关键应用中展示了其不可替代的价值和广泛的适用性。
15 2
|
2月前
|
Java Unix Linux
1.3 C语言的应用范围
C语言自20世纪80年代以来一直是主流编程语言,适用于小型计算机、个人电脑及大型机。因其高效紧凑且易于修改和移植,广泛用于软件开发。尽管后来C++和JAVA流行起来,但C语言仍然是软件行业核心,并在嵌入式系统、科学编程和操作系统开发如Linux中扮演重要角色。即使到现在,掌握C语言仍是一项重要技能。不是必须得是计算机专家才能使用C语言,学习C语言同时也能学到很多C++的知识。
50 8
|
4月前
|
C语言
C语言中的无参函数
C语言中的无参函数
208 1
|
自然语言处理 Java 编译器
你知道C语言吗?
了解C语言是什么,以及代码是怎么跑起来的,初学者必看
|
7月前
|
存储 算法 C语言
链队C语言的使用
链队C语言的使用
33 0
|
存储 编译器 C语言
初识C语言(四)
初识C语言(四)
|
C语言
一些最近总结的C语言要注意的问题和对于一些问题的理解
一些最近总结的C语言要注意的问题和对于一些问题的理解
50 0
|
C语言 C++
初识C语言(2)上
初识C语言(2)
62 0
|
C语言
C语言之宏详解(超级详细!)
C语言之宏详解(超级详细!)