拒绝从入门到入土:初识C语言

拒绝从入门到入土:初识C语言,第1张

目录


一、什么是C语言

1.C语言是与计算机交流的语言

2.计算机的组成

3.计算机语言的发展是从低级到高级的,C语言的发展也不例外。


4.什么是编译


二、第一个C语言程序

1.步骤

2.main函数的使用

3.其他问题


三、常量与变量

1.常见的输入输出格式

注:计算机中的单位(以32位 *** 作系统为例)

2.变量分类

3.变量的使用

4.变量的作用域和生命周期

5.常量


四、字符串、转义字符与注释

1.字符串

2.转义字符

3.注释


六、选择语句


七、循环语句


八、函数

九、数组

1.数组的定义

2.定义方式

3.数组下标

4.数组的打印——循环

5.变长数组

十、 *** 作符(或运算符)

1.算数 *** 作符

2.赋值 *** 作符

注意:'='是赋值,而'=='是判断相等

3.单目 *** 作符

(1)sizeof       *** 作数的类型长度(以字节为单位)

(2)前置++、-- 与后置++、--

(3)强制类型转换

4.条件 *** 作符

5.逗号表达式

6.下标引用、函数调用和结构成员

(1)下标引用 *** 作符:[]

(2)函数调用 *** 作符:()


一、常见关键字

1、常见关键字

2、变量的命名规则

3、部分常见关键字讲解

(1)auto(自动)

(2)typedef(类型重定义)

(3)static(静态)

(4)register(寄存器)


二、#define定义常量和宏

1.定义常量

2.定义宏


三、指针

1.内存

2.地址的生成

3.数据的储存

4、指针变量

5.解引用 *** 作符*

6.指针变量的连续定义

7.指针变量的大小


四、结构体

1.什么是结构体

2.结构体变量的打印


声明:以下只是本人对C语言的理解,如有错误请指正


一、什么是C语言 1.C语言是与计算机交流的语言

人与人之间交流需要语言,例如汉语、英语、法语等。


人与计算机交流同样需要语言,人和计算机交流用用计算机语言。


计算机语言现在已经有上千种,例如C/C++/Java/Python等。


2.计算机的组成

我们买回来的电脑从外表上来说就是一个铁疙瘩,但这只是电脑的硬件,要想使用电脑还需要有 *** 作系统等必要的组成部分。


          上层         🔺  应用软件:QQ、微信等

                                (我们普遍 *** 作在这里)

电脑 ————————————————————

                                *** 作系统:windows等

           

下层(或底层)🔻  驱动层(现已集成到电脑)

                                         硬件:硬盘等 

3.计算机语言的发展是从低级到高级的,C语言的发展也不例外。


(1)计算机是通过二进制工作的,最早人们使用0101001这样二进制的指令来用计算机处理问题,非常复杂

(2)后来逐渐产生了汇编指令(助记符)(ADD)将一部分二进制代码记为助记符

(3)逐渐演化出B语言,后来变成C语言,此时的C语言已经成为一门高级语言

(4)最早各公司使用的C语言标准各不相同,导致各公司间的代码不能互通,ANSI制定了C语言的国际标准:C89(也叫C90)、C99、C11等

(5)现在普遍支持的是C89

(6)C语言需要使用编译器,常用的主要有:GCC、MSVC等(MSVC是VS的编译器)

4.什么是编译

(1)C/C++是典型的编译型语言

过程:test.c(源代码)—编译—>test.obj—链接—>test.exe(可执行程序)

(2)Python是典型的解释型语言

过程:代码—解释器—>可执行程序


二、第一个C语言程序 1.步骤

(1)打开vs(推荐使用VS2013及以上版本,dev c++软件太旧,不适合学习)

(2)创建项目

(3)创建源文件(.c是源文件、.h是头文件)

(4)写代码

#include  
int main() 
{
     printf("hello world");
     return 0; 
}
2.main函数的使用

(1)main函数是程序的入口

(2)一个程序中只允许出现一个main函数(一个程序不能有两个入口)

(3)main函数的标准写法(不要用void main,这种写法太古老了)

int main()
{
    return 0;
}
//void main()   不推荐使用
//{
//    
//}

(4)return 0;指返回0这个整型值,与函数的返回类型int对应

(5)return的整数其实也可以是其他数字,不过在业界默认return 0表示正常退出,return 1表示异常退出

3.其他问题

(4)#include当需要使用C语言内置函数时都需要引用头文件才能使用,stdio表示standard input output(标准输入输出)

(5)printf是一个库函数,是一个专门用来打印数据的输出函数

(6)从头到尾调试:ctrl+F5


三、常量与变量 1.常见的输入输出格式

char

字符型

1byte

%c

short

短整型

2bytes

%d

int

整形

4bytes

%d

long

长整型

>=4bytes

%ld

long long

更长的整形

8bytes

%lld

float

单精度浮点型

4bytes

%f

double

双精度浮点型

8bytes

%lf

类型的作用:类型主要用于创建变量,创建变量的本质是向内存中申请一块空间

#include 
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(大于等于4小于等于8)
    printf("%d\n", sizeof(long long));//8
    printf("%d\n", sizeof(float));//4
    printf("%d\n", sizeof(double));//8
    printf("%d\n", sizeof(long double));//8
    return 0;
 }

其实感觉用的比较多的还是char、int、float、double着几种类型,其他的用得不多

注:计算机中的单位(以32位 *** 作系统为例)

bit

比特位

计算机最小单位,表示一个0或1

byte

字节

8bit

KB

千字节

1024bytes

MB

兆字节

1024KB

GB

吉字节

1024MB

2.变量分类

C语言变量分为局部变量和全局变量

(1)局部变量:不定义在{}内,在整个工程中可用

(2)全局变量:定义在{}内,在{}内可用

例如在以下程序中,main函数{}以外的global就是一个全局变量,local与global就是一个局部变量。


这里你可能会想,这两个global定义不是重复了吗?其实不然,当全局变量与局部变量数值冲突的时候,以局部变量为准,所以输出为2020而不是2019

#include 
int global = 2019;//全局变量
int main()
{
    int local = 2018;//局部变量
    //当局部变量和全局变量同名的时候,局部变量优先使用
    int global = 2020;//局部变量
    printf("global = %d\n", global);
    return 0; 
}
//输出:global = 2020
//

注:当一个全局变量在同一个工程中一个.c文件中定义时,也可以通过extern声明在另一个.c文件中使用

使用方法:

extern 类型 变量名;

extern int a;

test1.c

int a = 10;

test2.c

#include
extern int a;//声明变量a
int main()
{
    printf("%d", a);
    return 0;
}
//输出为10
3.变量的使用
#include 
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;
}

scanf函数用于输入,以下为使用方法:

#include
int main()
{
    int a = 0;//定义变量
    scanf("%d",&a);//&表示取地址,不能丢
    return 0;   
}
4.变量的作用域和生命周期

这两个概念比较类似,一个是空间上的概念,一个是时间上的概念

(1)作用域————空间

作用域是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。


*  局部变量的作用域是变量所在的局部范围。


(简单点说就是{}内部)

* 全局变量的作用域是整个工程。


(任意位置可用)

(2)生命周期————时间

变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段

* 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。


*  全局变量的生命周期是:整个程序的生命周期。


5.常量

C语言中的常量分为以下以下几种:

(1)字面常量

#include 
{
    3.14;//字面常量
    1000;//字面常量
    return 0;
}

(2)const 修饰的常变量

#include 
{
    //const 修饰的常变量
    const float pai = 3.14f;   //这里的pai是const修饰的常变量,f表示说明它是一个浮点数
    pai = 5.14;//是不能直接修改的
    return 0;
}

上面例子上的 pai 被称为 const 修饰的常变量, const 修饰的常变量在C语言中只是在语法层面限制了变量 pai 不能直接被改变,但是pai 本质上还是一个变量的,所以叫常变量。


以下代码可以验证:

#include 
{
    int n = 0;
    int arr[n] = {0}; 
    return 0;
}

这个代码是编译不过去的,因为在C89标准中,数组元素的数量是不能用变量表示的。


如果此处n为常量,那这个代码是可以编译成功的,间接证明了const修饰的这个变量是个无法直接改变的变量(可以通过指针的方式改变,不过这也是以后的事情了)

(3)#define 定义的标识符常量

#include 
//#define的标识符常量 演示
#define MAX 100
int main()
{ 
    printf("max = %d\n", MAX);
    return 0;   
}
//这个变量是没有类型的,就只是100

(4)枚举常量

#include
enum Sex//生活中有一些属性时可以直接列举出全部可能性的
{
 MALE,//内部储存为0
 FEMALE,//内部储存为1
 SECRET//内部储存为2
};//括号中的MALE,FEMALE,SECRET是枚举常量
int main()
{
    //枚举常量演示
    printf("%d\n", MALE);
    printf("%d\n", FEMALE);
    printf("%d\n", SECRET);
    return 0;   
}

注:枚举常量的默认是从0开始,依次向下递增1的


四、字符串、转义字符与注释 1.字符串

"hello world\n"

这种由双引号引起来的一串字符称为字符串字面值,或者简称字符串。


C语言没有字符串对应的数据类型。


注:字符串的结束标志是一个 \0 的转义字符。


在计算字符串长度的时候 \0 只是结束标志,不算作字符串的内容。


注意下面有一个小陷阱(包含数组的知识,可以先看看数组的知识):

#include
int main()
{
    char arr1[]="abcdef";
    char arr2[]={'a','b','c','d','e','f'};
    //这两个数组定义一的方式一样吗?
    printf("%s\n",arr1);
    printf("%s\n",arr2);
    printf("%d\n", strlen(arr1));
    printf("%d\n", strlen(arr2));
    //输出:
    //abcdef
    //abcdef烫烫烫烫烫abcdef(abcdef后面的部分随机)
    //6
    //22(随机值)
    //原因:第一种方式数组第7各元素为\0,而第二种方式数组只存储了六个字母,第一个可以找到
    //字符串的结束标志,而第二个找不到,只能不断地往后找直到找到\0,strlen函数以也以\0为停
    //止计数的标志,原理相同
    return 0;    
}

这两个数组定义一的方式一样吗?

答:不一样。


为什么打印两个数组的内容与计算数组的大小所得到的值不同?

原因:第一种方式数组第7个元素为\0,而第二种方式数组只存储了六个字母,第一个可以找到字符串的结束标志,而第二个找不到,只能不断地往后找直到找到\0,strlen函数以也以\0为停止计数的标志,原理相同

其实可以形象一点:

 

arr1

a

b

c

d

e

f

\0 (找到\0,结束)ヽ(✿゚▽゚)ノ

              别看了,玉女无瓜

arr2

a

b

c

d

e

f

(我\0呢?)w(゚Д゚)w

(算了接着往后数吧)┑( ̄Д  ̄)┍

2.转义字符

\?

在书写连续多个问号时使用,防止他们被解析成三字母词

\' 

用于表示字符常量'

\“

用于表示一个字符串内部的双引号

\\

用于表示一个反斜杠,防止它被解释为一个转义序列符。


\a

警告字符,蜂鸣(电脑会响,可以试一试)

\b

退格符

\f

进纸符

\n

换行

\r

回车

\t

水平制表符

\v

垂直制表符

\ddd

其中ddd表示1~3个八进制的数字。


 

如: \130就表示八进制数130ASCII码对应的字符

\xdd

其中dd表示2个十六进制数字。


 

如: \x30表示十六进制30ASCII对应的字符

以下为比较重要的转义字符的使用:

#include
int main()
{
    // \? 在书写连续多个问号时使用,防止他们被解析成三字母词
    printf("(are you OK??)\n");
    //??)在一些古老的编译器中会被解析为三字母词,输出为],现在的编译器已经不支持了
    //输出:(are you OK]
    printf("(are you OK\??)\n");
    //如果改成(are you OK\??)就将第一个?转义为不是三字母词的问号
    //输出即为(are you OK??)
    // \' 用于表示字符',而不再被作为表示字符的符号
    // \" 用于表示一个字符串内部的双引号,
    // \\ 用于表示一个反斜杠,防止它被解释为一个转义序列符。


    printf("%c",'\''); //输出:'     printf("\""); //输出:"     printf("\\n"); //输出:\n     //这里的\将特殊字符转义为普通的字符,加上\表示转义后面的字符     // \a 警告字符,蜂鸣     printf("\a");     // \n 换行     printf("\n");     // \r 回车     // \t 水平制表符 相当于键盘上的tab键     // \ddd ddd表示1~3个八进制的数字。


 如:\130 X     printf("%c",'\130');//注意这是一个字符     // \xdd dd表示2个十六进制数字。


 如:\x30 0     printf("%c",'\x63');//注意这也是一个字符     return 0; }

下面是一个小笔试题,可以拿来练练手:

"c:\test\628\test.c"

这个字符串有几个字符?

答案:c : \t e s t \62 8 \t e s t . c

总共14个,最容易错的感觉就是\628不是一个字符,因为一个八进制数不可能出现8

3.注释

(1)C语言注释法:

/*int a = 0;*/

缺点:不能嵌套注释

(2)C++注释法(推荐):

//int a = 0;

缺点: *** 作复杂


六、选择语句

如果你好好学习,找到好工作,就能走上人生巅峰;如果你不学习,毕业即失业,回家卖烤山芋。


示例:

#include 
int main()
{
    int coding = 0;
    printf("你会去敲代码吗?(选择1 or 0)");
    scanf("%d", &coding);
    if(coding == 1)
   {
       prinf("坚持,卷王就是你\n");
   }
    else
   {
       printf("放弃,摆烂了\n");
   }
    return 0; 
}

有句老话说得好:“读书破万卷。


”让我们一起努力,卷死舍友✌


七、循环语句

人类的本质是复读机,有些东西咱们就是需要不断重复,比如学习

#include 
int main()
{
    printf("学习编程\n");
    int line = 0;
    while(line<=20000)
   {
        line++;
        printf("我要继续努力敲代码\n");
   }
    if(line>20000)
        printf("好offer\n");
    return 0; 
}

C语言三大基本结构:顺序结构、选择结构、循环结构

滚去学习.jpg


八、函数

#include 
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 
int Add(int x, int y) //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; 
}

函数就如同一个工厂,原数据经过标准化的处理变为需要的新数据,以达到简化程序的作用

九、数组 1.数组的定义

一组相同类型元素的集合

2.定义方式

int arr[10]={0,1,2,3,4,5,6,7,8,9};

3.数组下标

数组的下标以0开始,各元素与所在位置差1,如果数组有10个元素,则下标的范围是0-9

int arr[10] ={0};
//int表示这个数组的b每个元素都是整形
//arr为数组名,[10]表示q数组共有s十个元素
//={0}表示对数组进行初始化,使数组的第一个元素等于零
4.数组的打印——循环
#include 
int main()
{
    int i = 0;
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    for(i=0; i<10; i++)
    {
        printf("%d ", arr[i]);
    }
    
    return 0;
}
//输出:1 2 3 4 5 6 7 8 9 10
5.变长数组

请观察以下代码:

int main()
{
    int arr1[10]={0};
    int n = 0;
    int arr2[n];
    return 0;
    //这两个数组的定义方式哪一个是正确的?
}

在C89标准中数元素个数的定义必须为常量表达式,例如:

int main()
{
    int arr1[10]={0};
    int arr2[4+6]={0};
    return 0;
    //这两个数组定义均正确
}

而在C99标准中支持了变长数组,数组的元素个数可以是变量,在C99标准下arr1和arr2的定义都是没有问题的

int main()
{
    int arr1[10]={0};
    int n = 0;
    int arr2[n];//变长数组
    //int arr2[n]={0};对变长数组进行了初始化,是不正确的
    return 0;
}

注:这个变长数组是不能初始化的且由于VS对C99语法的支持较差,所以这个代码在VS上编译是会报错的

十、 *** 作符(或运算符)

主要的C语言 *** 作符

算术 *** 作符:+ - * / %

移位 *** 作符:>>

位 *** 作符:& ^ |

赋值 *** 作符:= += -= *= /= &= ^=  |=    >>=  

单目 *** 作符( *** 作数为一, *** 作数表示 *** 作的内容的个数):

!           逻辑反 *** 作

-           负值

+           正值

&           取地址

sizeof       *** 作数的类型长度(以字节为单位)

~           对一个数的二进制按位取反

--          前置、后置--

++          前置、后置++

*           间接访问 *** 作符(解引用 *** 作符)

(类型)       强制类型转换     例如:(int)3.14

关系 *** 作符:

>     大于

>=      大于等于

!=     用于测试“不相等”

==      用于测试“相等”

逻辑 *** 作符:

&&     逻辑与

||          逻辑或

条件 *** 作符(这是唯一的一个三目 *** 作符):

exp1 ? exp2 : exp3

逗号表达式:

exp1, exp2, exp3, …expN

下标引用、函数调用和结构成员:

[] () . ->

其中一部分 *** 作符涉及二进制的问题,我们先跳过

1.算数 *** 作符

+、-、*、/、% 加、减、乘、除、取模

这几个 *** 作符没有太多需要注意的问题,不过要注意C语言的乘法用*,除法用/,%可以理解为取余

#include
int main()
{
    int a = 7/2;
    //七除二得三余一,结果为三
    int b = 7%2;
    //取模就相当于取余且取模 *** 作符的左右数字只能是整数
    printf("%d\n",a);
    printf("%d\n",b);
    //输出:
    //3
    //1
    //此时得到的a与b均为整数运算得到的值
    float a = 7.0/2;
    //当我们输入2.0这样的浮点型常量时,编译器会默认其为double类型
    float b = 7/2.0;
    //当我们需要进行小数运算时,只需要除号的左右的数字其一为浮点数即可
    printf("%.1f\n",a);
    printf("%.2f\n",b);
    //%f用于打印float类型的数据,前面的.1表示小数点后保留一位,.2表示保留两位
    //输出:
    //3.5
    //3.50
    return 0;
}
2.赋值 *** 作符

+=、-= 加等于、减等于

#include
int main()
{
    int a = 0;
    a = 20;
    a += 3;//a加等于3,即表示a = a + 3;
    printf("%d",a);//23
    a -= 3;//a减等于3,即表示a = a - 3;
    printf("%d",a);//20
    return 0;   
}
注意:'='是赋值,而'=='是判断相等 3.单目 *** 作符 (1)sizeof       *** 作数的类型长度(以字节为单位)
#include
int main()
{
    int arr[10]={0};
    printf("%d\n",sizeof(arr));//这里用%zu更严谨,%d也是可以的
    //计算整个数组的大小 40
    printf("%d\n",sizeof(arr[0]));
    //计算数组一个元素的大小 4
    printf("%d\n",sizeof(arr)/sizeof(arr[0]));
    //计算数组元素的个数 10
    int a = 0;
    printf("%d",sizeof(a));//这个地方的()是可以省略的,计算a的大小 4
    printf("%d",sizeof a);//这个代码也是正确的,内容一致 4
    //如果sizeof为函数则必须加上括号,这里不加括号也是正确的,因此sizeof只是一个 *** 作符而不是函数
    printf("%d\n",sizeof(int));
    //这个 *** 作符也可以计算类型的大小 4
    //printf("%d\n",sizeof int);是不对的必须加括号
    return 0;
}
(2)前置++、-- 与后置++、--

前置++与--:先使用,后++,--

后置++与--:先++,--,后使用

#include
int main()
{
    int a = 10;
    int b = a++;//先使用后++,表示先把10赋给b然后在在a的基础上加1
    printf("%d %d\n",a,b);//11 10
    a = 10;
    int c = a--;//同样,先使用后--,表示先把10赋给c然后在在a的基础上减1
    printf("%d %d\n",a,c);//9 10
    a = 10;
    int d = ++a;//先++后使用,表示先在a的基础上加1再把11赋给b
    printf("%d %d\n",a,d);//11 11
    a = 10;
    int e = --a;//同样,先--后使用,表示先在a的基础上减1再把9赋给b
    printf("%d %d\n",a,e);//9 9
    return 0;
}
(3)强制类型转换
#include
int main()
{
    int a =(int)3.14;//指把3.14这个double类型的常量强制转化为int类型并赋值给变量a
    //int a =int(3.14)  别括错了
    printf("%d\n",a);//打印3,浮点数强制类型转换为整数会丢失精度
    return 0;
}
4.条件 *** 作符

exp1 ? exp2 : exp3

如果第一个表达式exp1为真,则整个表达式的值为表达式exp2的值,否则整个表达式的值为表达式exp3的值

您看到这里可能会有些混乱,看看下面的代码就清晰多了

#include
int main()
{
    int a = 10;
    int b = 5;
    printf("%d",a>b ? a : b);//a > b,a,b对应三个表达式
    //a>b成立,表达式的值为a的值
    //类似于选择语句,找出其中的较大值,输出为10
    a = 1;//此时我们改变a的值
    printf("%d",a>b ? a : b);
    //a>b不成立,表达式的值为b的值
    //输出为5
    return 0;   
}
5.逗号表达式

逗号表达式就是逗号隔开的一组表达式

特点:从右向左依次计算,整个表达式的值是最后一个表达式的结果

#include
int main()
{
    int a = 10;
    int b = 20;
    int c = 0;
    int d = (c=a-2,a=b+c,c-3);
    //c=a-2=8,之后a=b+c=28,再之后c-3=5,最终表达式的值为5
    printf("%d",d);
    return 0;   
}
6.下标引用、函数调用和结构成员 (1)下标引用 *** 作符:[]
#include
int main()
{
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int n = 3;
    arr[n] = 20;//[]就是下标引用 *** 作符,arr和3都是[]的 *** 作数
    return 0;
}
(2)函数调用 *** 作符:()
#include
int add(int x,int y)
{
    return x+y;
}
int main()
{
    int sum = add(2, 3);
    //()是函数调用 *** 作符,add,2,3都是 *** 作数
    printf("%d\n",sum); 
    return 0;
}

一、常见关键字 1、常见关键字

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

循环语句中使用:for、while、do whlie、break、continue

选择语句中使用:if else、switch、case、default、goto

数据类型与返回类型:char、short、int、long、float、double、signed、unsigned、enum、struct、union、void

其他:auto、extern、register、static、return、sizeof、typedef

2、变量的命名规则
  • 有意义
  • 名字必须由字母、数字、下划线组成,不能有特殊字符,同时不能以数字开头
  • 变量名不能是关键字
3、部分常见关键字讲解 (1)auto(自动)

我们知道我们创建的变量无论是全局变量还是全局变量,都会在变量对应的生命周期开始时被创建,在生命周期结束后被销毁,这些变量其实在前面都应该加上一个auto,只不过被省略了而已(auto int a = 0;)

(2)typedef(类型重定义)

这个关键字可以重定义一些类型的命名,比如:

#include
int main()
{
    //C语言中有一个数据类型叫做无符号整数unsigned int
    //如果我想定义一个这样的无符号整型,我就需要这样写:
    unsigned int a = 0;
    //有些人觉得这样太麻烦了,那我们不妨给这个类型改个名字
    typedef unsigned int uint;
    //此时这样定义变量即可:
    uint b = 0;
    //可以偷个懒少打几个字
    return 0;
}

这个关键字的用法:

typedef 类型 你想改的名字

typedef unsigned int uint;

感觉对结构体比较好用

(3)static(静态)
  • 修饰局部变量——静态局部变量

请看下面的代码:

#include
int test()
{
	int a = 1;变量a创建
	a++;//a加1
	printf("%d ", a);//打印a的值
        //注意此处a会被销毁
}
int main()
{
	int i = 0;
	while (i < 10)//循环10次
	{
		test();//进入函数
		i++;
	}
	return 0;
}
//最后这个函数相当于这样运行了10次
//输出:2 2 2 2 2 2 2 2 2 2 

如果我们在a的前面加上static,结果会怎么样:

#include
int test()
{
	static int a = 1;//变量a创建,注意它不会再创建第二次,第二次经过这段代码后a不会变回1
	a++;//a加1
	printf("%d ", a);//打印a的值
        //此处a就不会被销毁,保持它原有的值
}
int main()
{
	int i = 0;
	while (i < 10)//循环10次
	{
		test();//进入函数
		i++;
	}
	return 0;
}
//最后这个函数运行了10次
//输出:2 3 4 5 6 7 8 9 10 11

加上static后我们发现输出的数据完全不一样,从表面上看只是变量出它本来的作用域后没有进行消除。


从本质上来看的话,这个变量a被储存在了静态区内。


从而延长了变量的生命周期。


那这个静态区是个嘛玩意儿呢?

在计算机的内存中有许许多多的区域,我们写程序时常用于存储地址的区域主要是:栈区、堆区和静态区

栈区

这里的空间主要储存局部变量

堆区

这里可以通过申请空间,来储存数据

静态区

主要储存静态变量

原本a应该储存在栈区内,加上static后就被储存到了静态区,这个变量的生命周期便成了整个程序。


  • 修饰全局变量——静态全局变量

还记得前面extern的代码吗?

这次我们在全局变量的a前加上static看看会发生什么事情。


test1.c

static int a = 10;

test2.c

#include
extern int a;
int main()
{
    printf("%d", a);
    return 0;
}
//输出为10

此时如果你运行这个程序,你会发现这个程序会报错,说这个a没有定义

介为嘛呢?

原因:static修饰全局变量的时候,这个全局变量的外部链接属性就变成了内部链接属性,其他的源文件(.c文件)就不可以再使用这个全局变量了。


我们在使用的时候就感觉这个变量的生命周期变短了。


  • 修饰函数——称为静态函数

test1.c

static int Add(int x, int y)
{
   return x+y; 
}

test2.c

#include
extern int Add(int x, int y);//extern声明函数
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; 
}

同样,如果你运行这个程序,你会发现这个程序会报错,说这个函数没有定义

介又似为嘛呢?

原因:static修饰函数的时候,这个函数本身的外部链接属性就变成了内部链接属性,其他的源文件(.c文件)就不可以再使用这个函数了。


说(水)了这么长时间,这个内部链接和外部链接到底似个嘛玩儿?(是个什么,天津方言,前面也是)

  • 什么是链接?

链接是C语言源代码变为可执行程序的必要步骤(包括编译与链接)

一个工程中是可以存在多个源文件(.c)的,在完成编译变成.obj文件后我们的几个.obj文件会相互链接,读取对方的内容以构成完整的程序。


当我们用static修饰全局变量和函数时,相当于把它们藏在了一个文件中,其他文件看不到。


(4)register(寄存器)
  • 寄存器

此刻需要引出我们电脑中常用的储存设备:寄存器、高速缓存、内存、硬盘

这些设备有以下的关系:

寄存器

越往上读取的速度越快,

空间越小,造价越高;

越往下读取的速度越快,

空间越大,造价越低。


高速缓存

内存(8G/16G)

硬盘(500G)

在计算机的发展中中,最早期的时候只存在内存的概念,数据主要存放在内存中,运行程序的时候CPU(中央处理器)必须去内存中读取数据来运行程序。


然而随着计算机的发展,CPU处理数据的效率越来越高,然而此时内存的读写速度已经跟不上CPU的处理速度了,这时就出现了高速缓存和寄存器,它们的读写速度更快,可以适应CPU的使用。


就好像垒墙,一个人叠砖头,一个人递砖头,那个垒墙的人速度越来越快,然而那个递砖的却没有加快速度。


此时,垒墙的人只能干瞪眼等着砖头,大大降低了工作效率。


然而读写速度越快,设备的造价越高,可容纳的数据容量越小。


计算机就选择了这样的运行方式:

数据有限存储在寄存器中,然后存储到高速缓存中,然后储存在内存中,在寄存器中的数据使用完成后,高速缓存的数据会跟进到寄存器中,内存的数据也会跟进到高速缓存中,在这样的数据处理下,大部分的数据都可以在寄存器中获得,这样就跟上了CPU的处理速度。


register后的变量表示建议放到寄存器中,只是建议,真正的放置需要电脑自身的分配。


不过现在我们的计算机已经十分先进了,你不写register它也知道哪个该放进寄存器中。



二、#define定义常量和宏 1.定义常量

使用方法:#define NUM 100

#include
#define NUM 100//此处NUM表示一个常量值为100,为了区别,这个常量名固定为大写
int main()
{
    printf("%d",NUM);
    return 0;
}
//输出:100
2.定义宏

使用方法:

#define 宏名(宏参数,宏参数) 宏体

#define ADD(x,y) ((x)+(y))

#define ADD(x,y) ((x)+(y))
//预处理指令不需要加分号,这里的x于y类似于函数中形参,但x与y没有类型
//而且此处最好加上(),也可以不加
int main()
{
	int a = 2, b = 3;
	int z = ADD(a, b);//ADD(a,b)被替换为((a)+(b))
	printf("%d", z);
	return 0;
}

我们要记住,宏只是作替换,这一点很重要。



三、指针

重头戏来了,那个被抱怨怎么也学不会的指针,它来了!!!

先别害怕,其实这东西没有那么难。


1.内存

程序的运行需要内存,我们为了有效地使用内存,就需要将内存划分为一个个小的内存单元,每一个单元的大小是一个字节。


(一个字节比较合理,这个内存单元太小也不好,太大也不好)为了 能够有效地使用每个内存单元,我们给每一个单元都定了一个编号,这个编号就叫做这个内存单元的地址。


就像在我们的生活中,比如说你在网上购物。


你就一定需要告诉商家,你自己的确切位置,比如说天津市南开区卫津路92号几斋(几号楼)几号宿舍。


(PS:希望以后有机会在那里收快递吧)这个内存的地址也是这个道理,就像楼中的门牌号,通过编号的方式,内存的单元地址也就确定了。


我们可以轻松地找到对应的地址,而不需要一个一个去找。


内存

编号(取十六进制数)

1byte

0x00000001(十进制:1)

1byte

0x00000002(十进制:2)

1byte

0x00000003(十进制:3)

1byte

0x00000004(十进制:4)

1byte

0x00000005(十进制:5)

1byte

0x00000006(十进制:6)

1byte

......

2.地址的生成

我们的电脑中都有硬件电路,用于生成地址的电线叫地址线。


当电路中有电路通过时,会产生正负脉冲,从而表示0与1.此处我们以三十二位电脑为例,它在生成地址时32根地址线同时产生电信号表示1或0,当每一个地址线组合起来时就有了许许多多的不同的排列组合方式。


我可以三十二个全为0:00000000000000000000000000000000——对应0

我也可以前三十一个全为0:00000000000000000000000000000001——对应1

......

这样的排序方式一共有2的32次方种,也就是说它有是4294967296种排序,内存中就一共有这么多个字节的空间。


但是这个数字不是很直观,我们先对它除以1024得到4194304个KB,再除1024得到4096个MB,再除以1024得到4GB,也就是说在早期的三十二位电脑内存中一共有4GB的内存空间。


3.数据的储存

这里我们打开VS2022,输入以下代码,可以通过调试找到a的地址

#include 
int main()
{
    int a = 10;
    &a;//取出num的地址,&为取地址符号
    //这里的num共有4个字节,每个字节都有地址,但我们取出的是第一个字节的地址(较小的地址)
    printf("%p\n", a);//打印地址,%p是以地址的形式打印
    return 0; 
}

具象化的话,就用以下内容表示:

内存

编号

1byte

0xFFFFFFFF

1byte

0xFFFFFFFE

1byte

......

a

0x0012FF47

0x0012FF46

0x0012FF45

0x0012FF44

1byte

......

1byte

0x0000002

1byte

0x0000001

我们实际上取出的只有0x0012FF47这个地址

我们在VS上的内存窗口上可以看到三行,包含以下内容:

地址

内存中的数据

内存数据的文本解析

0x0012FF47

a0 00 00 00

????(内容不定,没什么用处)

我们a的值为10,用二进制表示即为:0000 0000 0000 0000 0000 0000 0000 1010(二进制的数字表达最后一位表示2的0次方,倒数第二位就表示2的1次方,以此类推,十就是2的3次方加2的一次方也就是1010),在这个时候我们以每个四位为一组,就可以得到数据的表示方法:00 00 00 0a(在16进制数中,a表示10,b表示11,c表示12,d表示13,e表示14,f表示15)

0000 0000 0000 0000 0000 0000 0000 1010

   0       0       0       0       0       0       0       a

可能你又想问,这个数据好像存倒了。


其实这只是小端存储方式,这里先不做介绍。


4、指针变量

看到这里,指针变量终于出场了

请看以下代码:

#include
int main()
{
    int a = 10;
    int* p = &a;
    //我们把a这个变量的地址储存在这个变量p中,这个p就叫做指针变量,类型为int*
    return 0;
}

编号代表地址,而地址也可以成为指针,有一定的指向作用,所以叫做指针变量。


简单地说指针就是地址。


那我们如何理解这个int* p = &a;呢?

(1)中间的*表示p是个指针变量,注意指针变量是p,而不是*p

(2)int表示指针指向的对象是整形

(3)p为指针变量,接受&a的内容,也就是变量的首地址

在了解这些后,我们也可以创建其它类型的指针变量,比如:

#include 
int main()
{
    char ch = 'w';
    char* pc = &ch;//字符型变量的指针
    *pc = 'q';
    printf("%c\n", ch);
    return 0; 
}
//输出:q
5.解引用 *** 作符*
int main()
{
	int a = 10;
	int* p = &a;
	printf("%d", *p);//这里的*p表示对指针变量p解引用,找到其对应的内容
	return 0;
}
//输出:10

注意:int*p = a;中的*不表示解引用,通过解引用符号,我们可以轻易地找到指针地址的对应值

6.指针变量的连续定义

我曾经说过,定义指针的*表示p是个指针变量,所以我想说明一下指针的连续定义

int main()
{
	
    int* p1,p2,p3 = &a;
    //这个定义方式是不正确的,只有p1是指针变量而其他两个是整型变量
    int* p1,*p2,*p3;
    //这个定义方法才是正确的,在第一种方法下,*只给第一个变量使用
    return 0;
}

这里的指针的int、char等其实在本质上是读取数据的方式不同,int表示每次读取四个字节,char表示每次读取一个字节,这个我们到以后的指针进阶再慢慢讨论。


7.指针变量的大小
#include 
int main()
{
    printf("%d\n", sizeof(char *));
    printf("%d\n", sizeof(short *));
    printf("%d\n", sizeof(int *));
    printf("%d\n", sizeof(double *));
    return 0; 
}

你可能认为输出结果是:1 2 4 8

但实际上是:4\8 4\8 4\8 4\8(4或8)

介似为嘛捏?

原因:指针变量储存的是地址,也就是说指针变量的大小取决于存放一个地址需要的空间的大小,32位平台下地址是32个bit位(即4个字节),而64位平台下地址是64个bit位(即8个字节),所以指针变量的大小就是4或8.

结论:指针大小在32位平台是4个字节,64位平台是8个字节。



四、结构体 1.什么是结构体

结构体是C语言中重要的知识点,结构体使C语言有能力描述复杂的类型。


比如描述一个学生,一个学生包含: 名字+年龄+性别+学号。


我们用基本的数据类型没有办法描述这样的复杂对象,这里就只能使用结构体来描述了。


struct Stu//struct表示创建结构体,Stu表示这个数据类型叫struct Stu
{
    char name[20];//名字
    int age;      //年龄
    char sex[10];  //性别
    char id[15]; //学号
};
//通过结构体将这些简单数据类型集合来描述一个复杂的对象
struct Stu s ={"zhangsan",18,"male","2021001227"};
//定义一个struct Stu类型的结构体变量s
//初始化时只需要输入内容,然后用逗号隔开,但一定要注意对应
2.结构体变量的打印
#include
struct Stu
{
    char name[20];
    int age;
    char sex[10];
    char id[20];
};
void print(struct Stu* ps)//用一个结构体变量接收
{
    printf("%s %d %s %s\n", (*ps).name, (*ps).age, (*ps).sex, (*ps).id, (*ps).age, (*ps).sex, (*ps).id);
    //(*ps)得到了结构体变量s,这个.表示在结构体内部找到变量
    //.的用法:结构体对象+内部变量名
    //(*ps).name:将ps解引用找到结构体,再找到name变量
    printf("%s %d %s %s\n",*ps->name, *ps->age, *ps->sex, *ps->id);
    //ps->就表示在指针指向的结构体中找到对应的变量
    //->的用法:结构体指针变量+内部变量名
    //ps->name:找到指针变量指向的结构体内的name变量
}
int main()
{
    struct Stu s = { "zhangsan",18,"male","2021001227" };//初始化
    struct Stu* p = &s;//这是一个结构体类型的指针,储存了结构体变量的地址
    print(p);//将结构体指针传入函数
    return 0;
}
//输出:zhangsan 18 male 2021001227

结束语:这只是C语言的概述,很多细致的内容没有展开,要想学习C语言,只有这些知识是远远不够的。


 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/607895.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-14
下一篇 2022-04-14

发表评论

登录后才能评论

评论列表(0条)

保存