初步认识 C语言

引言

1. C语言 是一门计算机语言。
发展历程:机器语言 => 汇编语言 => B 语言 => C语言

计算机语言:人和计算机交流的语言。(C、C++、Java、Go)
人与人沟通的语言:汉语、英语…

2. C语言 广泛应用于底层开发,例如:Linux 操作系统就是用 C语言 写的。它是一个面向过程的语言,而 C++、Java 是面向对象的语言。其中,Java 就是 由 C语言 编写的。

3. 二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为 C语言 制定了一套完整的美国国家标准语法,称为 ANSI-C,作为 C语言 最初的标准。目前最新的标准为 C11.

4. C语言 编译器主要有 Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C等。其中 Linux 用的是 GCC,VS 系列底层用的是 MSVC.

一、C语言 使用的 IDE

IDE:Integrated Development Environment (集成开发环境)

C 语言常使用 Windows IDE 是微软公司旗下的 Visual Studio.

使用 Visual Studio 步骤:

① 创建新项目 => 空项目 => 选择路径创建 => 创建一个 " .c 源文件 "

1-1

② 可以在一个项目中,创建多个源文件,之后就可以写代码了。

1-2

二、第一个 C语言 程序

#include <stdio.h>

int main() 
{
	printf("hello world\n");
	return 0;
}

输出结果:

1-3

注意事项:

① main 称为主函数,它本身就是一个函数。此外,我们应该明白:main 函数是一个程序的入口,一个工程中(Project) 有且仅有一个。

② main 前面的 " int " 为主函数的返回类型,它和最后面的 " return 0; " 对应了起来。

③ " printf " 称为格式化输出函数,它是由 C语言 官方提供的库函数,如果我们想要使用库函数,就需要在程序的最开始引入对应的头文件,即 " #include <stdio.h> ".
stdio 为标准输入输出的意思。(standard - input - output)

三、C语言 的数据类型

注意: C 语言没有字符串类型。

// 1. 内置数据类型
char 				// 字符数据类型
short 				// 短整型
int 				// 整形
long 				// 长整型
long long 			// 更长的整形
float 				// 单精度浮点数
double 				// 双精度浮点数

// 2. 自定义类型
struct 				//结构体
enum 				//枚举
union 				// 联合体
...

程序清单:

#include <stdio.h>

int main() 
{
	printf("%d\n", sizeof(char)); 		// 1
	printf("%d\n", sizeof(short)); 		// 2
	printf("%d\n", sizeof(int)); 		// 4
	printf("%d\n", sizeof(long)); 		// 4
	printf("%d\n", sizeof(long long)); 	// 8
	printf("%d\n", sizeof(float)); 		// 4
	printf("%d\n", sizeof(double)); 	// 8
	
	return 0;
}

注意事项:

① sizeof 是一个操作符,它可以用来计算变量所占内存大小,计算单位是 " 字节 "。

② 计算机的基本单位:bit (比特位):只能由 1 或 0 构成。

byte (字节): 1 byte = 8 bit
KB:1 KB = 1024 byte
MB:1 MB = 1024 KB
GB:1 GB = 1024 MB
TB:1 TB = 1024 GB

四、变量与常量

C 语言经常使用变量与常量来描述一些概念。变量是用来描述变化的数据;常量是用来描述不变的数据。

int age; 			// 用变量描述年龄
char ch = 'a'; 		// 用变量描述一个字符

#define NAME JACK 	// 用常量描述一个人的名字

五、局部变量与全局变量

通俗理解,局部变量就是在大括号内,全局变量就是在大括号外;当局部变量和全局变量重复存在时,大括号内的程序以局部变量为准。

#include <stdio.h>

int a = 123; 		// 全局变量
void test() 
{
	printf("%d\n", a);
}

int main() 
{
	int a = 100; 	// 局部变量
	printf("%d\n", a);
	test();
	return 0;
}

// 输出结果:
// 100
// 123

六、变量的作用域和生命周期

注意事项:

实际上,关于局部变量和全局变量,它们的概念与变量的作用域和生命周期有关。
变量的作用域:一个变量名在一个程序中所能起作用的范围;
变量的生命周期:指的是变量的创建到变量的销毁之间的一个时间段。

① 局部变量的作用域是变量所在的局部范围,一般来说,局部变量的作用域就是它所在的一个函数内部;而全局变量的作用域是整个工程。

② 局部变量的生命周期是从进入作用域生命周期开始,出作用域生命周期结束;全局变量的生命周期是整个程序的生命周期。

③ 一个全局变量不给初始化,默认为0;而一个局部变量不给初始化,默认是随机值(在 VS 编译器下会直接报错)

七、变量的创建与使用

变量的创建,需要在底层开辟内存空间。

int a = 10; // 把 10 赋值给 变量 a

1-4

八、常量

常量分为四种:
字面常量、const 修饰的常变量、#define 定义的标识符常量、枚举常量。

1. 字面常量

字面常量很好理解,就是我们平时看到的值。

#include <stdio.h>

int main()
{
	3.1415926; // 字面常量
	100;
	'a';
	return 0;
}

2. const 修饰的常变量

被 const 修饰的变量,具有常属性,但本质上被修饰的量依旧是一个变量。

程序清单1

#include <stdio.h>

int main()
{
	const int a = 10;
	
	// a = 20; // error1
	// int arr[a] = { 0 }; // error2
	
	return 0;
}

注意事项:

① 在上面代码的注释中,error1 表示 常变量a 不能被修改
② error2 表示在定义一个数组元素数量时,括号中应该填入常量 (暂不考虑变长数组)

程序清单2

#include <stdio.h>

int main() {

	int a = 10;
	int b = 100;

	const int* pa = &a;
	pa = &b;      // √
	//*pa = 20;   // error1

	int* const pa2 = &a;
	*pa2 = 20;    // √
	//pa2 = &b;   // error2
	
	return 0;
}

注意事项:

① const 处于 * 号的左边,表示指针 pa 指向的对象不能通过解引用来改变。
② const 处于 * 号的右边,表示指针 pa2 本身地址不可被改变。

3. #define 定义的标识符常量、宏

被 #define 修饰的量作为标识符常量,是我们在 C语言 中最普遍的常量使用方式。此外,#define 也可以定义宏,和函数的功能类似。

注意写法:被 #define 修饰的常量或宏,字母应为大写,整个表达式后面不跟逗号。

#include <stdio.h>

// define 定义常量
#define MAX 100
#define STR "hello world"

// define 定义宏,宏带有参数
#define ADD(x,y) (x+y)

int main() {
	printf("%d\n", MAX); // printf("%d\n", 100);
	printf("%s\n", STR); // printf("%s\n", "hello world");

	int a = 10;
	int b = 20;
	int sum = ADD(a, b); // int sum = (a+b);
	printf("%d\n", sum); 

	return 0;
}

// 输出结果:
// 100
// hello world
// 30 

4. 枚举常量

枚举就是一一列举的意思,生活中有些概念可以被我们人为一一列举出来,如:性别、三原色。性别不是男就是女、三原色为 rgb. 所以,C语言 提供了一种自定义类型,称为 enum,即枚举。

被一一列举的东西叫做枚举常量,也属于一种 C语言 的常量。

#include <stdio.h>

enum SEX
{
	MALE,
	FEMALE
};

enum COLOR
{
	RED,
	GREEN,
	BLUE
};

int main()
{
	// RED = 3; // error
	
	printf("%d\n", RED);
	printf("%d\n", GREEN);
	printf("%d\n", BLUE);

	return 0;
}

// 输出结果:
// 0
// 1
// 2

注意事项:

① 注意枚举的写法,枚举大括号内的量称为枚举常量,不能被修改。
② 枚举常量的值默认是从 0 开始,依次向下递增。

九、字符串

程序清单:

#include <stdio.h>
#include <string.h>

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abc\0def";
	char arr3[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
	char arr4[] = { 'a', 'b', 'c', 'd', 'e', 'f', '\0'};

	printf("%s\n", arr1);
	printf("%s\n", arr2);
	printf("%s\n", arr3);
	printf("%s\n\n", arr4);

	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));
	printf("%d\n", strlen(arr3)); // 随机值
	printf("%d\n", strlen(arr4));

	return 0;
}

输出结果:

1-5

调试窗口:

1-6

注意事项:

① 在 C语言中,被双引号括起来是字符串字面值,简称为字符串。同样地,字符串也只能被双引号括起来。

② 字符串的结束标志是一个 ’ \0 ’ 的转义字符。在使用格式化输出时,’ \0 ’ 的作用相当于告诉了编译器,它是一个停止的标志。在使用 strlen 这个库函数计算字符串的长度时,也是一样的道理,它只计算 ’ \0 ’ 之前的长度。

③ 对比 arr1 和 arr3 这两个创建字符数组的方式,可以发现,由于在 C语言 中,内存具有连续性,所以如果没有 ’ \0 ’ 作为结束标志,就会导致我们使用 printf / strlen 的时候,一直向后找 ’ \0 ’ 这个结束标志。

1-7

十、转义字符

转义字符释义
\?在书写连续多个问号时使用,防止他们被解析成三字母词
\’表示字符常量单引号
\"用于表示一个字符串内部的双引号
\\用于表示一个反斜杠
\a警告字符,蜂鸣器
\b退格符
\f进纸符
\n换行
\r回车
\t水平制表符
\v垂直制表符
\ddd将八进制数字转换成十进制数字 (ddd表示1 ~ 3个八进制的数字)
\xdd将十六进制数字转换成十进制数字 (dd表示 2 个十六进制的数字)

对最后两个转义字符进行验证:

#include <stdio.h>
#include <string.h>

int main()
{
	printf("%c\n", '\135'); // 1*8^2 + 3*8^1 + 5*8^0 = 93
	printf("%c\n", '\x65'); // 6*16^1 + 5*16^0 = 101

	printf("%d\n", strlen("\t\628")); // '\t' + '\62' + '8' (三个字符)
	return 0;
}

输出结果:

1-8

注意事项:

① 第一个输出结果,C语言 通过转义字符,是先将 " 八进制 135 " 转换成 " 十进制 93 " 存储到内存中,当我们需要以字符的形式格式化输出 93 时,它再根据 ASCII 码表转换成了对应的符号。第二个输出结果,也是同样的道理。

② 第三个输出结果,strlen 是用来求字符串的长度的,由于八进制中只有 0~7 生效,所以上面的 ’ \t\628 ’ 相当于 ’ \t ’ + ’ \62 ’ + ’ 8 ’ 这三个字符组成的。

十一、C语言的注释写法

方式一:不能嵌套注释
/*

*/

方式二:可以注释一行也可以注释多行
//

十二、顺序、选择、循环

C语言 的程序设计和我们日常生活息息相关,在我们日常生活中,基本上所有的事情都包括了顺序、选择、循环这三个方式。

① 顺序:我们平时写的代码就是自一个方法进入,之后再从此方法出来,这就是顺序执行的结构。

② 选择:if 语句、if - else 语句、if - else if - else 语句…

③ 循环:while 语句、for 语句、do … while 语句…

十三、数组

数组是一组相同类型元素的集合。

int arr[10] = {1,2,3,4,5,6,7,8,9,10}; // 定义一个整形数组,最多放 10 个元素

下面的代码涉及到变长数组的概念,在定义一个数组的时候,可以允许指定数组的大小为一个变量。

#include <stdio.h>

int main()
{
	int n = 10;
	int arr[n];
	return 0;
}

另外,上面面的代码在 VS 编译器下可能会报错,但实际上是因为 VS 不支持 C99标准。而在 Linux 中使用 GCC 编译器则不会报错。

十四、操作符

// 1. 算数操作符
+ - * / %

// 2. 移位操作符
>> <<

// 3. 位操作符
& ^ |

// 4. 赋值操作符
= += -= *= /= &= ^= |= >>= <<=

// 5. 单目操作符
! 					// 逻辑反操作
- 					// 负值
+ 					// 正值
& 					// 取地址
sizeof 				// 操作数的类型长度(以字节为单位)
~ 					// 对一个数的二进制按位取反
-- 					// 前置、后置--
++ 					// 前置、后置++
* 					// 间接访问操作符(解引用操作符)
(int) 				// 强制类型转换为 int

// 6. 关系操作符
>
>=
<
<=
!= 				// 用于测试“不相等”
== 				// 用于测试“相等”

// 7. 逻辑操作符
&& 				// 逻辑与
|| 				// 逻辑或

// 8. 条件操作符
a ? b : c

// 9. 逗号表达式
exp1, exp2, exp3...

// 10. 其他操作符
[]            // 下标引用操作符
()			  // 函数调用操作符
.			  // 结构体变量.成员名
->			  // 结构体指针变量->结构体成员

十五、关键字

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 的处理速度。

1-9

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

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

register int a = 10;

3. static 关键字

(1) static 修饰局部变量

程序清单1
#include <stdio.h>

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

int main()
{
	for (int i = 0; i < 10; i++)
	{
		test();
	}
	return 0;
}

// 输出结果:
// 2 2 2 2 2 2 2 2 2 2 
程序清单2
#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;
}

// 输出结果:
// 2 3 4 5 6 7 8 9 10 11

总结:

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

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

1-10

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

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

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

(2) static 修饰全局变量

程序清单1:

2-1

程序清单2:

2-2

总结:

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

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

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

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

2-3

(3) static 修饰函数

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

2-4

十六、指针

1. 指针与内存

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

#include <stdio.h>

int main() 
{
	int a = 10;
	int* pa = &a; 		// 取出 a 的地址赋值给指针变量 pa
	*pa = 20;		 	// *pa 等价于 a
	
	printf("%d\n", a);
	return 0;
}

// 输出结果:20

在上面的程序中,

&a 表示取出 int变量 a 的地址 (取出的是 变量a 的第一个字节地址);
*pa 表示解引用 指针变量 pa,*pa 就等价于 a.

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

2-5

注意事项:

内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是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));		// 4
	printf("%d\n", sizeof(pc));		// 4
	printf("%d\n", sizeof(pd));		// 4
	
	return 0;
}

结论:

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

① 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;
}

// 输出结果:
// Jack 18 32
// Jack 18 32
// Jack 18 32

注意事项:

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

程序清单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;
}

// 输出结果:
// Jack 18 32
// 小红  17  25

注意事项:

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

student1.name = "小红"; // error
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十七ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值