第一节 初识C语言
本章重点:
- 什么是C语言
- 第一个C语言程序
- 数据类型
- 变量、常量
- 字符串+转义字符+注释
- 选择语句
- 循环语句
- 函数
- 数组
- 操作符
- 常见关键字
- define定义常量和宏
- 指针
- 结构体
一. 什么是C语言
- 概述
自然语言:汉语,日语,英语……
人和人交流的语言
计算机语言:C,C++,JAVA……
人和计算机交流的语言,
已知计算机语言一共有上千种。
C语言是一种计算机语言,广泛应用于底层开发(操作系统,驱动程序……)。
- C语言标准
二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言制订了一套完整的美国国家标准语法,称为ANSIC,作为C语言最初的标准。目前2011年12月8日,国际标准化组织(ISO)和国际电工委员会(IEC)发布的C11标准是C语言的第三个官方标准,也是C语言的最新标准,该标准更好的支持了汉字函数名和汉字标识符,一定程度上实现了汉字编程。
二. 第一个C语言程序
- 使用vs2019创建C语言项目。
点击创建新项目
点击空项目,点击下一步
配置项目名称,项目位置,点击创建
项目创建成功
- 新建.C文件
右击源文件,点击添加-新建项
填写名称,更改位置,点击添加
- 写代码
写一个c语言程序,打印helloworld
//C语言中所有的字符都是英文的字符
#include<stdio.h>
//主函数 是程序的入口,写的C语言代码都是从main函数的第一行开始执行的
//main函数是必须有的,但是有且仅有一个
int main() {
printf("helloworld\n");
//printf 是库函数 - C语言的标准库中提供的一个现成的函数-直接可以使用
//功能是在屏幕上打印信息
//库函数的使用,是需要包含头文件的,printf需要的头文件叫:stdio.h
return 0;
}
使用ctrl+f5将代码编译-链接-运行
三. 数据类型
C语言中常见数据类型:
int main(){
//sizeof计算结果的单位是字节
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
return 0;
}
四. 变量与常量
- 概述
不变的值是常量,变得值是变量。
- 定义变量
- 只能由字母、数字和下划线组成
- 不能以数字开头
- 长度不能超过63个字符
- 变量名中区分大小写的
- 变量名不能使用关键字
- 变量的名字尽量有意义
int main(){
int age = 150;
float weight = 45.5f;
char ch = 'w';
return 0;
}
- 变量的分类
全局变量:在{}外边定义的变量就是全局变量
局部变量:就是{}内部定义的变量就是局部变量
当前局部和全局变量在一个地方都可以使用的时候,局部优先
int a = 100;//全局变量
int main(){
int a = 1000;//局部变量
{
int b = 0;
printf("%d\n", b);
}
printf("%d\n", a);
return 0;
}
- 变量的使用
写一个代码
完成2个整数的相加,并输出结果
printf 是输出函数
scanf 是输入函数
int main()
{
int a = 0;
int b = 0;
int s = 0;
//输入2个值
scanf("%d %d", &a, &b);//3 5
//计算
s = a + b;
//输出
printf("%d\n", s);
return 0;
}
注意:若是遇到了scanf报错问题,详见博客:https://danbaku.blog.csdn.net/article/details/131155663
- 变量的作用域和生命周期
作用域:
通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
1.局部变量的作用域是变量所在的局部范围。
2.全局变量的作用域是整个工程。
例1:
#include<stdio.h>
int main() {
{
int a = 10;
printf("%d", a);
}
return 0;
}
运行程序,代码正确。
把printf语句放在括号外面时,再次尝试运行代码:
#include<stdio.h>
int main() {
{
int a = 10;
}
printf("%d", a);
return 0;
}
代码报错,运行失败。
报错显示为“未定义的标识符a”
出现此问题的原因就是作用域的问题,因为a的作用域就只在上面的花括号中,超出作用域范围。
因此:
1.局部变量的作用域是变量所在的局部范围。
例2:
#include<stdio.h>
int a = 100;
int test() {
printf("%d", a);
return 0;
}
int main() {
printf("%d\n", a);
test();
return 0;
}
运行成功,输出如下。
此时的a在test()和main()中均可调用。这里的a是全局变量。
因此:
2.全局变量的作用域是整个工程。
全局变量的很强大,那么可以跨文件使用吗?
看如下例子:
首先新建了一个a.c文件,并在文件中定义a。
然后再回到之前的文件中,加入一句extern
#include<stdio.h>
extern int a;
int test() {
printf("%d", a);
return 0;
}
int main() {
printf("%d\n", a);
test();
return 0;
}
运行成功,输出如下。
生命周期:
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段。
1.局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
2.全局变量的生命周期是:整个程序的生命周期。
- 常量
C语言中的常量和变量的定义形式有所差异。
C语言中的常量分为一下几种:
字面常量(直接写出的值,例如:1,”a”等等)
const 修饰的常变量
#define定义的标识符常量
枚举常量
例子如下:
#include<stdio.h>
//const常属性的
//修饰变量
int main() {
int a = 10;
printf("a=%d\n", a);
a = 100;
printf("a=%d\n", a);
return 0;
}
运行成功,结果如下。
这时,a是一个变量,经过第二次的重新赋值后变为了100。
现在我们加上“const”去修饰变量a。
#include<stdio.h>
//const常属性的
//修饰变量
int main() {
const int a = 10;
printf("a=%d\n", a);
a = 100;
printf("a=%d\n", a);
return 0;
}
此时运行代码,发现程序报错。
这时变量a变得不可修改。a就是const修饰的常变量,但是本质上a还是一个变量。
a是被const在语法层面上加入了一个限制,a具有了常属性。
那么如何证明a的本质上还是一个变量呢?
例子如下:
#include <stdio.h>
int main() {
int arr[10] = { 0 };
int n = 10;
int arr2[n] = { 0 };
}
运行失败,代码报错。
修改代码,加上const。
#include <stdio.h>
int main() {
int arr[10] = { 0 };
const int n = 10;
int arr2[n] = { 0 };
}
运行失败,代码仍然报错。
此时证明,a加上了const本质上还是变量,a只是具有了常属性。
当然,如果你照着上面的步骤完成了编译运行,并且没有报错的情况,那么看看这个文件的后缀,如果文件后缀为.cpp,那么上面的代码是可以运行的,并且不会报错的。这是因为在C++中,是将const看作常量的。而c语言不是。所以,这也是为什么在前面说要将文件后缀改为.c的原因之一。
#include<stdio.h>
//#define定义标识符常量
#define A 100
int main() {
printf("%d\n", A);
int B = A;
printf("%d\n", B);
}
运行成功,结果如下。
这个可以算作是常量,可以用上面的方法尝试证明,这里不做过多演示。
#include<stdio.h>
//枚举常量
//枚举:一一列举
//自定义类型,颜色
//枚举类型
enum color
{
//枚举类型里面列的值是枚举常量
red, //0
green, //1
blue //2
};
int main() {
enum color a = red;
printf("%d", a);
return 0;
}
运行成功,结果如下。
值默认从0开始,依次递增1。值可以改,具体在后面的笔记中详细介绍。
五. 字符串+转义字符+注释
- 字符串
C语言中有字符类型,但是没有字符串类型,那么在C语言中是如何表示字符串的呢?
“hello world”这种有双引号引起来的一串字符称为字符串字面值,或者简称字符串。注意:字符串的结束标志是一个\0的转义字符,在计算字符串长度的时候\0是结束标志,不算作字符串内容。
如何观察到\0?
首先,生成一段空代码。
#include<stdio.h>
int main() {
return 0;
}
然后按F10,开始调试。这里会弹出窗口和终端等东西,不用管。
注意行号左边是否有黄色箭头,有箭头就是进入了调试模式。
在上方菜单栏中依次点击:窗口-监视-监视1(1)。
在弹出的监视1中输入:”abcd”,输入完成后展开。这时我们便看到了\0。
字符串的本质是一串字符,可以存放在字符数组里面。
这里举个例子:
#include<stdio.h>
int main() {
char arr1[] = "abc";
char arr2[] = {'a','b','c'};
return 0;
}
我们继续用刚刚的方法进行调试。
数组的区别显而易见。
这里需要注意,监视的时候一定要执行到return,否则会出现代码乱码等问题。例如下图。
如何判断执行到哪一句代码,只需要看黄色箭头。
调试监视器时,可以在把鼠标光标放在returen前面,这时会出现绿色箭头,点击即可。
我们可以再来研究一下有没有斜杠零的区别。
代码如下:
#include<stdio.h>
int main() {
char arr1[] = "abc";
char arr2[] = {'a','b','c'};
printf("%s\n", arr1);
printf("%s", arr2);
return 0;
}
代码运行成功,但并非预期输出。
这里可以看出来,字符串中,隐藏着放着一个\0,而\0是字符串的结束标志。
我们对arr2手动加入\0,观察效果。
#include<stdio.h>
int main() {
char arr1[] = "abc";
char arr2[] = {'a','b','c','\0'};
printf("%s\n", arr1);
printf("%s", arr2);
return 0;
}
代码运行成功。
现在,我们对字符串的长度进行研究。
例子如下:
#include<stdio.h>
#include<string.h> //字符串头文件
int main() {
char arr1[] = "abc";
char arr2[] = {'a','b','c'};
/*printf("%s\n", arr1);
printf("%s", arr2);*/
//strlen是一个库函数,求字符串长度的, 统计的是字符串中\0之前的字符个数
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));//随机值
return 0;
}
代码运行成功。
再次说明,一定要注意strlen库函数求字符串长度是不算上\0的。
- 转义字符
转义字符,即转变原来的意思,
假如我们要在屏幕上打印一个目录: c:\code\test.c
#include <stdio.h>
int main()
{
printf("c:\code\test.c\n");
return 0;
}
运行成功,但非预期结果。
#include <stdio.h>
int main()
{
printf("c:\\code\\test.c\\n");
return 0;
}
对代码进行转义,再次运行,得到正确结果。
下面列举出了C语言的常用转义字符。
注意:部分编译器会支持三字母词。大部分都是一些早期的编译器。举一些常见的参数值:??)的值是],??(的值是[。为了防止这种效果,只需要再前两个问号前面加入\,将其三字母词问号转换为正常问号。
举例:打印单引号和双引号
#include <stdio.h>
int main()
{
//问题1:在屏幕上打印一个单引号',怎么做?
//问题2:在屏幕上打印一个字符串,字符串的内容是一个双引号“,怎么做?
printf("%c\n", '\'');
printf("%s\n", "\"");
return 0;
}
运行成功,结果如下。
- 注释
代码中有不需要的代码可以直接删除,也可以注释掉。代码中有些代码比较难懂,可以加一下注释文字。
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
/*C语言风格注释
int Sub(int x, int y)
{
return x-y;
}
*/
int main()
{
//C++注释风格
//int a = 10;
//调用Add函数,完成加法
printf("%d\n", Add(1, 2));
return 0;
}
注释有两种风格:
C语言风格的注释 /xxxxxx/
缺陷:不能嵌套注释
C++风格的注释 //xxxxxxxx
可以注释一行也可以注释多行
六. 结构
C语言是一个结构化的程序设计语言。共有三种结构,顺序结构、选择结构、循环结构。选择结构有两种,if和switch。循环结构有三种,while循环、for循环和do while循环。
- 选择语句
例子如下:
#include <stdio.h>
int main()
{
int coding = 0;
printf("你会去敲代码吗?(选择1 or 0):>");
scanf("%d", &coding);
if (coding == 1)
{
printf("坚持,你会有好offer\n");
}
else
{
printf("放弃,回家卖红薯\n");
}
return 0;
}
- 循环语句
C语言中如何实现循环呢?
while语句
for语句
do…while语句
例子如下:
//while循环的实例
#include <stdio.h>
int main()
{
int line = 0;
while (line <= 20000)
{
line++;
printf("我要继续努力敲代码\n");
}
if (line > 20000)
printf("好offer\n");
return 0;
}
七. 函数
函数的特点就是简化代码,代码复用。
例子如下:
#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1, &num2);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
// 上述代码,写成函数如下:
#include <stdio.h>
int Add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1, &num2);
sum = Add(num1, num2);
printf("sum = %d\n", sum);
return 0;
}
八. 数组
- 数组定义
C语言给了数组的定义:一组相同类型元素的集合。
例子如下:
#include<stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//定义一个整形数组,最多放10个元素
int arr2[10] = { 1,2,3,4,5 };//不完全初始化,剩余元素默认初始化为0
return 0;
}
我们可以通过“监视”看到数组情况。
- 数组的下标
C语言规定:数组的每个元素都有一个下标,下标是从0开始的。
数组可以通过下标来访问的。
例子如下:
#include<stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%d\n", arr[7]);
arr[7] = 100;
printf("%d\n", arr[7]);
return 0;
}
运行代码成功,结果如下。
- 数组的遍历
代码如下:
#include <stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int i = 0;
while (i < 10) {
printf("%d\n", arr[i]);
i++;
}
}
运行代码成功,结果如下。
九. 操作符
- 算术操作符
+ - * / %
/,除法运算时,如果两边都是整数,得到的商也是整数。
%,取模操作符两端只能是整数,不能是小数。
- 移位操作符
>> <<
移位移动的是二进制位。
- 位操作符
& ^ |
也是使用二进制位进行计算。
- 赋值操作符
= += -= *= /= &= ^= |= >>= <<=
- 单目操作符
单目操作符只有一个操作数,注意区别于双目操作符(二元操作符)。
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置-- 后置--,先使用,后减一;前置--,先减一,后使用
++ 前置、后置++ 后置++,先使用,后加一;前置++,先加一,后使用
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
关于自增自减运算符的一些魔鬼考试题详见博客:https://danbaku.blog.csdn.net/article/details/131756318
- 关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
- 逻辑操作符
&& 逻辑与
|| 逻辑或
- 条件操作符(三目操作符)
exp1 ? exp2 : exp3
- 逗号表达式
exp1, exp2, exp3, …expN
从左向右依次计算,整个表达式的结果是最后一个表达式的计算结果。
- 下标引用,函数调用和结构成员
[] () . ->
十. 常见关键字
C语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,用户自己是不能创造关键字的。
- auto
自动,和类型有关。所有的局部变量都是auto类型,所以auto可省略。常见格式为 auto int a;
- break
跳出当前循环,常用于循环和switch语句中。
- case
用于switch语句中。
- continue
继续,用于循环。
- default
用于switch语句中。
- do
常在do…while循环中。
- enum
枚举,和类型有关。
- extern
声明外部符号。
- register
寄存器,与存储类型有关。
- signed
有符号的,与类型有关。
- unsigned
无符号的,与类型有关。
- static
静态,与存储类型有关。是用来修饰变量和函数的。
在C语言中static有三种使用方式。
修饰局部变量-称为静态局部变量
修饰全局变量-称为静态全局变量
修饰函数-称为静态函数
先来说说static修饰局部变量。
例子如下:
#include<stdio.h>
void test()
{
int i = 1;
i++;//++i; i = i+1;
printf("%d ", i);
}
int main()
{
int j = 0;
while (j < 5)
{
test();
j = j + 1;
}
return 0;
}
运行代码成功,结果如下。
上面代码的思路是:main函数中有while循环,一共循环5次,也就是调用了5次test函数。现在再来看看test函数,函数中有变量i,i的值为1,i自增,输出i,所以这时的结果是2。循环中一共调用5次test函数,每次调用i都被赋值为1并自增。所以一共输出了5个2。
修改代码,对于变量i加上static修饰。
代码如下:
#include<stdio.h>
void test()
{
static int i = 1;
i++;//++i; i = i+1;
printf("%d ", i);
}
int main()
{
int j = 0;
while (j < 5)
{
test();
j = j + 1;
}
return 0;
}
运行代码成功,结果如下:
对于这个结果,不难理解,在这五次调用test函数时,i在调用时不会被重新赋值为1了,每一次i的值都是上一次i使用后的值。
这时局部变量就是静态的局部变量。
一个普通的局部变量进入函数创建,出函数销毁。但是被static修饰之后,进入函数时已经创建好了,出函数的时候也不销毁,多次调用函数时,共享一个变量。
主管的感受:生命周期变长了,但是作用域不变,只能在局部范围内使用。
本质是:普通的局部变量是放在栈区上的,但是被static修饰后,是存放在内存的静态区的,静态区的变量生命周期和全局变量的生命周期一样。
在这里我们仍可以用“监视”去看一看。
在代码窗口中我们可以看见static int i = 1这一句代码并没有被执行,而是直接跳到了下一行。这是因为在编译的时候就已经生成了该地址。
好,我们接下来继续看static修饰全局变量。
例子如下:
#include<stdio.h>
int g_val = 100;
void test()
{
printf("test():%d\n", g_val);
}
int main()
{
printf("%d\n", g_val);
test();
return 0;
}
运行代码成功,结果如下。
根据上述代码及结果,我们可以看到变量g_val是全局变量。接着对代码进一步进行改动。创建add.c,将全局变量放入add.c中。
再次编译代码,运行成功。
注意:extern的作用是声明来自外部文件的符号,上面截图中37行代码的是告诉编译器:g_val 是来自其他文件的。在C语言中每个.c文件都是单独经过编译器处理的。所以这里需要用extern进行声明。否则会报错。
全局变量是具有外部链接属性。这种属性决定了全局变量在多个文件之间可以互相使用。
再次修改代码,使用static修饰全局变量g_val。
代码如下:
运行代码失败,程序报错。
这是因为static修饰全局变量的时候,将外部链接属性变成了内部链接属性。g_val变量被static修饰,使得这个全局变量只能在当前的.c文件内部使用,不能在其他的.c文件中使用了。给我们的感受:改变了作用域。
最后,我们来讨论一下static修饰函数。
static修饰函数与static修饰全局变量用法上几乎没有区别。函数也是具有外部链接属性的。这种属性决定了函数是可以跨文件使用的。static修饰函数是把函数的外部链接属性改成了内部链接属性,使得函数只能在自己所在的.c文件中使用,不能在其他.c文件中使用。
- struct
结构体,与类型有关。
- typedef
类型重定义,类型重命名。
例子如下:
#include<stdio.h>
//将unsigned int 重命名为uint,所以uint也是一个类型名。
typedef unsigned int uint;
int mian() {
//在这里a和b这两个变量类型是一样的
unsigned int a = 0;
uint b = 0;
return 0;
}
- union
联合体类型。
- volatile
易变。
注意:以上关键字在用到的时候会逐个介绍。
十一. #define定义常量和宏
代码如下:
#include<stdio.h>
//define定义标识符常量
#define MAX 1000
//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;
}
十二. 指针
- 内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。编号=地址=指针
- 变量的地址
变量是创建内存中的(在内存中分配空间的),每个内存单元都有地址,所以变量也是有地址的。取出变量地址如下:
#include <stdio.h>
int main()
{
int num = 10;
#//取出num的地址
//注:这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)
printf("%p\n", &num);//打印地址,%p是以地址的形式打印
return 0;
}
代码运行成功,结果如下。
取地址一般取的是第一个字节的地址。
我们也可以使用vc++2019里面的内存窗口进行查看。
- 通过指针更改变量的值
例子如下:
#include<stdio.h>
int main()
{
int a = 10;
//& - 取地址操作符
int * pa = &a;//0x0012ff48 -内存的编号==地址==指针, pa叫指针变量
//* 是在说明pa是指针变量
//int 是在说明pa指向的是int类型的变量
*pa = 20;//* 解引用操作符 - 通过地址找到地址所指向的对象。*pa就等价于a
printf("%d\n", a);
return 0;
return 0;
}
运行代码成功,结果如下:
以整型指针举例,可以推广到其他类型,如:
#include <stdio.h>
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'q';
printf("%c\n", ch);
return 0;
}
代码运行成功,结果如下:
总结:
内存会被划分以字节为单位的一个个的内存单元
每个内存单元都有编号,编号 =地址=指针
C语言中创建的变量,其实是向内存申请一块空间,比如:int a = 10,就是向内存申请4个字节的空间,每个字节都有地址
&a的时候,拿出的是4个字节中地址较小的那个字节的地址(编号)
这个地址要存储起来,给一个变量,这个变量是用来存放地址(指针)所以叫指针变量:int *pa = &a;
pa中存放的是a的地址,要通过pa中的地址找到a,怎么写?*pa–> 通过pa中的地址找到a *pa = 20;//本质是修改a
补充:
编号 = 地址 = 指针
指针变量:存放地址的变量
口头语中说的指针:一般指的是指针变量
- 指针变量的大小
指针变量的大小取决于地址的大小
32位平台下地址是32个bit位(即4个字节)
64位平台下地址是64个bit位(即8个字节)
例子如下:
#include <stdio.h>
int main()
{
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(short*));
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(double*));
return 0;
}
运行代码成功,结果如下:
由此可得出结论:指针大小在32位平台是4个字节,64位平台是8个字节。
这里可以对vc++2019的位数进行更改来证明。
将x86改为x64
运行代码成功,结果如下:
十三. 结构体
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含: 名字+年龄+性别+学号 这几项信息。
这里只能使用结构体来描述了。
例子如下:
#include<stdio.h>
struct Stu
{
char name[20];
int age;
float score;
};
int main()
{
int num;
struct Stu s1 = { "张三", 20, 88.0f };
struct Stu s2 = { "李四", 18, 65.5f };
struct Stu s3 = { "王五", 19, 99.8f };
struct Stu* ps1 = &s1;
printf("%s %d %f\n", ps1->name, ps1->age, ps1->score);
//printf("%s %d %f\n", s1.name, s1.age, s1.score);
//printf("%s %d %f\n", s2.name, s2.age, s2.score);
//printf("%s %d %f\n", s3.name, s3.age, s3.score);
//结构体变量.结构体成员
//结构体指针->结构体成员
return 0;
}
本篇博客为本人学习C语言时的详细笔记,如有错误之处,还望各位指正。
文章为原创,如要转载请注明出处