C语言---初始C语言

简介: 本章介绍了C语言的基础知识,包含:数据类型,操作符,分支个循环语句,数组,指针,结构体等等基础知识。

1、初始C语言

1、编译器主要有:Clang、GCC、WIN-TC、MSVC、Turbo C等

什么是编译?

test.c-----------编译--------链接---------->test.exe

这个过程需要经过编译链接等过程,而众多编译器实现的功能就是把我们写的test.c进行编译。

2、VS2022(IDE)安装

官网:https://visualstudio.microsoft.com/zh-hans/?rr=https://www.microsoft.com/zh-cn/

image.png

image.png

文件说明:

  • .c结尾的文件称为源文件
  • .h结尾的文件称为头文件

下面创建一个源文件

image.png

image.png

3、第一个C语言项目:

#include <stdio.h>
int main()
{
   
   
    printf("hehe\n");         
    return 0;
}

//VS中点击F5运行
//C语言中一定要有main函数,main函数是程序的入口
//标准的主函数的写法
int main()
{
   
   
    return 0;
}
//这里有个对应的关系,return “0”和int相对应,int是整数类型,0是整数
//printf是个库函数,是别人开发好我们可以直接使用的,所以说在使用之前我们需要声明一下,所以“#include <stdio.h>”就是个说明
//std---代表标准的意思,i---代表input,o---代表output,所以说stdio.h就是标准的输入输出的头文件

4、数据类型

char                    //字符数据类型
short                   //短整型
int                     //整形
long                    //长整型
long long               //更长的整形
float                   //单精度浮点数
double                  //双精度浮点数
int main()
{
    printf("%d\n", 100);       "%d"以整形的方式打印,可以把"%d"---改为"%zu"
    return 0;
  • 每一个数据类型有多大呢?这里使用一个库函数来显示出来
int main()
{
   
   
    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;
}

1
2
4
4
8
4
8
//以上单位为“字节”
//可以发现int数据类型大小怎么和long的数据类型大小一样呢?
//原因:C语言规定:sizeof(long)>=sizeof(int)

4.1、%+字母

%d-----------打印整型(有符合整型)

%u-----------用于打印unsigned int num = 0这样的数据类型,这是无符号整型

//有符号整形包含正负数,无符号整型不包含复数。

%c-----------打印字符

%s-----------打印字符串

%f-----------打印float类型的数据

%lf----------打印double类型的数据

%zu/zd----------打印sizeof()的返回值

%p-----------打印地址

%02d--------------输出02/05...这样的效果

%2d---------------输出 2/ 5   //也就是说前面的0被空格替代了。

%-2d-------------左对齐

%.1f--------------打印小数点后一位的小数

float a = 0.0f       //float类型数据进行初始化

double a = 0.0       //double类型数据进行初始化
    //一般的直接赋值3.14编译器认为它是double类型的数据,为不是float类型的。
a=2 c=1 b=5 8
a=2 c=1 b=7 10
            12
            14
            16

4.2、计算机单位

bit---------比特位,计算机二进制101010101010。存储一个“1”或者“0”就占用一个bit位

byte-------字节 ,1字节=8bit

kb----------1kb=1024个byte

Gb----------

tb------------

pb------------

4.3、简述变量的本质

int main()
{
    int age = 22;         //age = 22赋值的这个过程叫做初始化,如果不进行赋值初始化,那么这个变量是随机值,这样一来这个变量不好掌控,所以尽量进行初始化。
    return 0;
}

//int整数类型大小为4字节,实际上就是向内存申请了一个4字节的内存大小,命名为age,用来存储“22”这个数据的。

5、变量和常量:局部变量和全局变量

不变的量/不可修改的量,C语言中用常量来表示,变得值C语言中用变量来表示

5.1、定义变量的方法

int main()
{
    int age = 22;
    return 0;
}

5.2、变量的分类

  • 局部变量
  • 全局变量
#include <stdio.h>

int global = 2019;    //全局变量
int main()
{
    int local = 2018;
    int global = 2020;     //局部变量
    printf("global = %d\n", global);          //那这里global值为什么?
    return 0;
}

输出:
    2020

所以可以得出结论:当全局变量和局部变量名字相同的情况下,局部变量优先。当然如果只有全局变量那么就会显示全局变量

错误代码示例:同一个范围里面不能声明重复的变量:

int main()
{
    int local = 2018;
    int local = 2020;    
    return 0;
}
//会报错

5.3、变量的使用---写一个代码,计算2个整数的和

#include <stdio.h>
//scanf是一个输入函数
int main()
{
    int num1 = 1;
    int num2 = 2;              //初始化

    //输入两个整数
    scanf("%d %d",&num1,&num2);

    //求和
    int sum = num1 + num2;

    //输出
    printf("%d\n", sum);
    return 0;

}

此时运行会发现报如下错误:

image.png

解决办法:在源文件的第一行写入如下代码:

#define _CRT_SECURE_NO_WARNINGS

//但是我们也能看到提示说可以用scanf_s这个函数代替,值得说明一下:scanf_s是VS编译器自己提供的函数,而非标准C提供的函数。

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

作用域:作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的名字并不是总是有效的/可用的

简单来说就是:这个变量在哪里起到作用/使用,哪里就是它的作用域

  • 局部变量的作用域是变量所在的局部范围
  • 全局变量的作用域是整个工程

5.4.1、局部变量的作用域是变量所在的局部范围

代码演示:

#include <stdio.h>
int main()
{
    {
        int a = 10;
        printf("a=%d\n", a);           //这个可以输出
    }
    printf("a=%d\n", a);               //但这个就会报错
    return 0;
}

但如果是这样的呢?

#include <stdio.h>
int main()
{
    int a = 10;
    {

        printf("a=%d\n", a);
    }
    printf("a=%d\n", a);
    return 0;
}


输出:
    a=10
    a=10
//此时a变量的作用域是整个的第一个大括号,所以都会输出

5.4.2、全局变量的作用域是整个工程

代码演示:

#include <stdio.h>
int a = 10;
int main()
{
    {
        printf("a=%d\n", a);
    }
    printf("a=%d\n", a);
    return 0;
}

输出:
    a=10
    a=10

5.4.2.1:补充:声明来自外部的符号

extern int a;      //声明变量
extern int Add(int x,int y);         //声明函数

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

1、局部变量的生命周期是:进入作用域之后生命周期开始,出作用域生命周期结束/销毁。

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

比如:

#include <stdio.h>

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

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

输出:
    2 2 2 2 2 2 2 2 2 2

这个重点就是看test()中a变量的声明周期,a变量从调用test函数到{开始就创建了,然后到}就销毁了,所以说输出一直是22222222...,而不是2,3,4,5,6...

6、常量

常量就是不变的量/不可修改的量

C语言中的常量和变量的定义形式有所差异。

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

  • 字面常量
  • const修饰的常
  • define定义的标识符常量

  • 枚举常量

6.1、字面常量

#include <stdio.h>
//1.字面常量
int main()
{ 
    14;          //整形常量
    3.14;       //浮点型常量
    'w';        //字符常量,单个字符用单引号''
    "asdf";     //字符串常量,字符串用双引号""
    return 0;
}

6.2、const修饰的常

为啥叫做常变量呢?首先我们先来看一下常规的变量:

int main()
{ 
    int a = 10;
    a = 20;                 //进行变量赋值,变量改变,最终输出20
    printf("%d\n", a);
    return 0;
}

上面的a变量是可以被修改的,那如果我们不要修改呢?我们只需要声明const即可

int main()
{ 
    const int a = 10;            //
    printf("%d\n", a);
    return 0;
}

补充:在C语言中,const修饰的a,本质是变量,但是不能被修改,有常量的属性。

那能不能修改const修饰的常变量呢?可以。我们可以使用指针,直接跳过const这个层面去修改。

如下代码:

#include <stdio.h>
int main()
{
    const int num = 10;
    int* p = &num;
    *p = 20;
    printf("%d", num);
    return 0;
}

那现在又有要求了,如果const声明的变量就这样被改来改去的,那还了得,能不能有办法直接不能修改呢?

可以!!!我们只需要把指针变量也用const声明即可。

#include <stdio.h>
int main()
{
    const int num = 10;
    const int* p = &num;      //const修饰的是*p
  //int const* = &num            //这个和上面的效果一样  
    *p = 20;                //这个就会直接报错。
    printf("%d", num);
    return 0;
}

const int* p = \&num;

const放在*的左边。意思是:p指向的对象不能通过p来改变了,但是变量本身的值是可以改变的。

int* const p = \&num;

const放在*的右边。意思是:p指向的对象是可以通过p来改变的,但是不能修改p变量本身的值。

const int* const p = &num

那这样就是上面两种情况的集合。无论是p变量本身还是p指向的对象都不能在修改。

6.3、define定义的标识符常量

#define    MAX 100                       //MAX就是个标识,这里定义的值也可以为字符串,浮点型...
#include <stdio.h>
int main()
{
    printf("%d\n", MAX);
    return 0;
}

 输出:
    100

define定义的常量既可以用来直接打印也可以用来赋值。

#define    MAX 100
#include <stdio.h>
int main()
{
    printf("%d\n", MAX);
    int a = MAX;
    printf("%d\n", a);
    return 0;
}

输出:
    100
    100

6.4、枚举常量

#include <stdint.h>

enum Color
{
    //下面的三个变量就是枚举常量
    RED,
    GREEN,
    BLUE
};

int main()
{
    enum Color c = RED;
    return 0;
}

7、字符串

1、首先char是字符类型,并不是字符串类型,那么C语言中有没有字符串类型呢?答案是没有!

那我们怎么表示字符串呢?使用双引号""来表示

"hello bit.\n"

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

注:字符串的结束是一个\0的转义符。在计算字符串长度的时候\0是结束标志,不算作字符串内容。

2、那我们怎么存储字符串呢?我们如何认识到\0的重要性呢?

#include <stdio.h>
int main()
{
    char arr1[] = "abcdef";         //存储字符串的形式
    char arr2[] = { 'a','b','c','d','e','f' };
    printf("%s\n", arr1);
    printf("%s\n", arr2);            //%s是以字符串的形式显示
    return 0;
}

输出结果:

image.png

为什么会出现如此现象呢?

  • 首先分析第一个,arr1数组,字符串后面隐藏了\0,所以程序在执行时,遇见了\0便结束了,所以说输出就是abcdef
  • 那么第二个arr2数组,没有\0所以程序在'f'之后并不会结束,直到什么时候遇见\0什么时候程序才会停止。因此输出以上内容。

那如果arr2数组我们向正常输出呢?我们可以手动添加一个\0

char arr2[] = { 'a','b','c','d','e','f',"\0"};

!!!可见字符串中\0的重要性

那如果是这样的呢?

#include <stdio.h>
#include <string.h>
int main()
{
    char arr[3] = { 'b','i','t' };
    printf("%d\n", strlen(arr));
    return 0;

输出:

image.png

为什么呢?明明我声明了arr[3],arr数组有三个元素,但为什么该字符串的长度还是随机值呢?因为我们赋值初始化就三个元素,而我恰恰声明了三个空间,所以‘t’后面并没有\0,所以会一直检索到后面的\0才会输出。

那换一种写法:

char arr[4] = { 'b','i','t' };     //上述代码就改变这一行,arr[3]--->arr[4]

输出:3

原因:我们声明了分配4个存储空间,而后面要存储的元素只有3个,所以第四个空间直接是0值,相当于\0的作用,所以到此停止,该字符串的长度为3。

3、下面我们在举个例子说明下\0的重要性

这里我们使用一个库函数:strlen---用于输出字符串的长度,这个需要头文件:#include

#include <stdio.h>
#include <string.h>
int main()
{
    char arr1[] = "abcdef";
    char arr2[] = { 'a','b','c','d','e','f'};
    printf("%d\n", strlen(arr1));
    printf("%d\n", strlen(arr2));
    return 0;
}

输出结果:

image.png

同样的道理arr1遇见f后面的\0之后就停止了,并且\0不算字符串内容,所以输出字符串长度为6

而arr2遇见f后面并没有\0,所以不会停止,继续向后遍历,直到遇到\0,以你输出不是6而是33

那如果想要arr2正常输出字符串长度我们只需要手动添加\0即可

char arr2[] = { 'a','b','c','d','e','f','\0'};

8、转义字符

常见转移字符,下面所有的转义字符,都是一个字符,注意:是一个字符

转义字符 释义
\? 在书写连续多个问号时使用,防止它们被解析成三字母词
\' 用于表示字符常量'
\" 用于表示一个字符串内部的双引号
\\ 用于表示一个反斜杠。防止它被解释为一个转义序列符
\a 警告字符,蜂鸣
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ddd ddd表示1-3个八进制的数字,如:\130 输出:X
\xdd dd表示两个十六进制数字,如:\x30 输出:0

后面两个转义符代码演示:

#include <stdio.h>
int main()
{
    printf("%c\n", '\130');   //特别强调,'\130'不是4个字符,而是一个字符
    printf("%c\n", '\x62');   //同理,'\x62'不是4个字符,而是一个字符
    return 0;
}

输出:

image.png

8.1、关于转义字符的一道题

统计下面字符串的长度

#include <stdio.h>
#include <string.h>
int main()
{
    printf("%d\n", strlen("c:\test\628\test.c"));
    return 0;
}

答案:14

分析:\t算一个字符,\62算一个字符,所以一共由14个字符

那为啥\62算一个字符呢?我们说\ddd是需要三个数字的呀?为什么后面的“8”不算呢?

因为\ddd表示的是八进制数字,八进制数字是从0~7的,是不包含8的,所以只有\62算一个转义符。

9、注释

10、选择语句和循环语句

1、选择语句

if (aa == 1)
{
    printf("ok,去玩");
}
else
{
    printf("废了");
}

2、循环语句

C语言中如何实现循环呢?

  • while语句
  • for语句
  • do......while语句
while ()
{

}

11、函数

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int n1 = 0;
    int n2 = 0;
    //输入
    scanf("%d %d", &n1, &n2);
    //求和
    int sum = Add(n1,n2);
    //输出
    printf("%d\n",sum);
    return 0;
}

image.png

12、数组

问题:要存储0~9的数字,怎么存储?

C语言中给数组的定义:一组相同类型元素的集合

#include <stdio.h>
int main()
{
    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
    printf("%d\n", arr[3]);       //遍历数组
    return 0;
}

arr[10],这个10,就是表示我要存储10个int类型的元素,当然不写10也行,程序会自行分配

并且这些元素在内存中都是有顺序的,从0开始......

这些序号,叫做数组的下标,下标是从0开始的。

遍历全部元素:

#include <stdio.h>
int main()
{
    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
    int i = 0;
    while (i < 10)
    {
        printf("%d\n", arr[i]);
        i++;
    }
    return 0;
}

那如果是这样的呢?

int arr[10] = { 0 };

那这10个空间存储的全部是0

image.png

13、操作符

13.1、算数操作符

+    -    *    /(得整数部分)    %(取模,得余数)

//  7 / 2    ----->3
//  7 % 2    ----->1

//那如果想要打印出小数呢?只需要两端有一个浮点数就会执行浮点数得出发
#include <stdio.h>
int main()
{
    float a = 7 / 2.0;
    printf("%.1f\n",a);          //.1就是保留小数点后一位
    return 0;
}

13.2、移位操作符

>>   <<

13.3、位操作符

&   ^   |

13.4、赋值操作符

=   +=   -=   *=   /=   &=   ^=   |=   >>=   <<=
  • a = a+3 a += 3是一样的效果,那后面的那都是这个原理

13.5、单目操作符

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

在C语言中0代表假,非0代表为真

13.5.1、!的介绍

#include <stdio.h>
int main()
{
    int flag = 0;           //flag初始化为0,所以flag此时为假
    if (!flag)              //那既然flag为假,那么!flag就为真
        printf("现在为真");
    return 0;
}

13.5.2、sizeof的使用介绍

#include <stdio.h>
int main()
{
    int a = 10;
    printf("%d\n", sizeof(a));        //4
    printf("%d\n", sizeof(int));      //4
    printf("%d\n", sizeof a);         //因为size是个操作符,所以不带()也可以使用   输出:4

    return 0;
}

当然sizeof也可以统计数组的类型大小

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%d\n", sizeof(arr));            //40
    printf("%d\n", sizeof(arr[0]));        //4,
    printf("%d\n", sizeof(arr) / sizeof(arr[0]));       //计算数组中元素的个数的方法
    return 0;
}

13.5.3、--、++的使用

这里只说明一个++即可,--的使用同理

1、先说后置++,eg:a++,后置++遵循一个原则:先使用,后++

#include <stdio.h>
int main()
{
    int a = 10;
    int b = a++;           //先使用,后++,也就是先 int b = a,然后在a = a+1
    printf("%d\n", b);
    printf("%d\n", a);
    return 0;
}

输出:
    10
    11

2、前置++,eg:++a。前置++遵循一个原则:先++,后使用

#include <stdio.h>
int main()
{
    int a = 10;
    int b = ++a;           //先++,后使用,也就是先 a = a+1,然后在int b = a
    printf("%d\n", b);
    printf("%d\n", a);
    return 0;
}

输出:
    11
    11

13.5.4、强制类型转换

#include <stdio.h>
int main()
{
    int a = (int)3.14;
    printf("%d\n", a);
    return 0;
}

输出:
    3

13.6、关系操作符

>
>=
<
<=
!=          //用于测试不相等
==          //用于测试相等

13.7、逻辑操作符

&&       逻辑与/并且
||       逻辑或/或者

13.8、条件操作符

exp1?exp2:exp3

exp1 ? exp2 : exp3
 真     算     不算      那么整个表达式的结果就是exp2表达式的结果
 假    不算     算       那么整个表达式的结果就是exp3表达式的结果

代码示例:

#include <stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int r = a > b ? a : b;      //比较最大值
    printf("%d\n", r);
    return 0;
}

输出:
    20

image.png

分析:如果a>b(exp1)成立,则a(exp2)是对的,那么b(exp3)是错的,因此整个表达式的结果就是exp2的结果,因为a=10,所以整个表达式的结果就是10,所以存进r=10

但是这里显然b(exp3)是对的,又因为b=20,所以r=20来进行存储,所以输出最大值为20。

13.9、逗号表达式

exp1,exp2,exp3,...expN

逗号表达式就是用逗号隔开的一串表达式。

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

#include <stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int c = 0;
    int d =(c = a - 2, a = b + c, c - 3);    //最后一个表达式c-3=5,所以最终输出结果5
    printf("%d\n", d);
    return 0;
}

输出:
    5

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

[]   ()   .   ->

13.10.1、下标引用

printf("%d\n", arr[0]);

13.10.2、函数调用

Add()

14、常见关键字简介

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

关键字是C语言内置的

15、关键词typedef和static

15.2、关键字typedef

typedef顾名思义是类型定义,这里应该理解为类型重命名

typedef只能对类型进行重命名

比如:

#include <stdio.h>
//将unsigned int 重命名为unit,所以unit也是一个类型名
typedef unsigned int unit;
int main()
{
    //观察num1和num2,这两个变量的类型是一样的
    unsigned int num1 = 0;
    unit num2 = 0;
    return 0;
}

15.3、关键字static

在C语言中:

static是用来修饰变量和函数的

  • 修饰局部变量---称为静态局部变量
  • 修饰全局变量---称为静态全局变量
  • 修饰函数---称为静态函数

知识:全局变量和静态变量放在静态区里面,如果不初始化,默认会被初始化为0

局部变量放在栈区,不初始化,默认值是随机值。

15.3.1、static修饰局部变量

我们先看看段代码:

#include <stdio.h>

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

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

输出:

image.png

分析:还记得上面所说得变量得生命周期,变量得生命周从进入作用域开始创建,到出作用域销毁。

在第一遍调用test函数时,n这个变量从{开始创建,到}开始销毁,然后中间n++,所以最终打印出来n=2,这个时候test函数调用完毕,此时已经没有n变量这个东西了,因为已经销毁了,然后第二遍调用test函数,然后从头开始创建n变量并赋值n=1,然后打印n=2,然后销毁n变量,所以最终打印效果:2 2 2 2 2 2 2 2 2 2

那么ok下面进入整体,我们使用static关键词,来修饰局部变量,看看有什么效果:

#include <stdio.h>

test()
{
    static int n = 1;                //这里使用static关键词修饰局部变量n变量
    n++;
    printf("%d ", n);
}

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

输出:

image.png

???为什么打印得效果不一样了呢?

原因分析:同样第一遍调用test函数,创建n变量并赋值n=1,然后n++,然后打印n=2,重点来了:由于n变量用static关键词修饰了,那么此时n变量即使出了此作用域后,n变量依然不会被销毁,这就是static关键词的作用此时n=2被储存起来了,然后第二遍调用test函数,此时“static int n = 1;”这一句话就相当于不起作用也能够了,由于时n=2,然后n++,所以n=3了,最后出test函数后,n变量依然不会被销毁,n=3就被存起来了,就这样循环下去,所以输出结果就如上。

总结:static修饰局部变量的时候,局部变量出了作用域,局部变量不会被销毁。

本质上,static修饰局部变量的时候,改变了变量的存储位置。

在细致解剖一下就是:

一块大内存,是分区域存储的,分为:栈区、堆区、静态区

image.png

变量被static修饰后,此变量就从栈区移动到了静态区,但代码这里n直接就是在静态区了,不存在转换,这里只是特殊说明一下,所以说static修饰局部变量的时候,改变了变量的存储位置。

15.3.2、static修饰全局变量

先来看段代码:

image.png

分析:我们把g_val变量放在一个源文件中,然后在另一个源文件中调用打印g_val变量,(两个源文件在同一个项目中)此时只需要使用关键字extern声明一下就行了

然后我们使用static关键字修饰一下g_val,这时在另一个源文件中就算使用关键字extern声明g_val变量,但g_val依然不能被使用:

image.png

总结:全局变量时具有外部链接属性的,而一旦static修饰全局变量的时候,这个全局变量的外部链接属性就变成了内部链接属性,其它源文件就不能在使用这个全局变量了,那这个全局变量的作用域由整个工程变为了当前文件,且此时静态全局变量是存储在静态区里面的。

15.3.3、static修饰函数

先来看一段代码:实现两个数相加

#include <stdio.h>

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

int main()
{
    int a = 10;
    int b = 20;
    int c = Add(a, b);
    printf("%d\n", c);
    return 0;
}

输出:
    30

上面代码使用了一个Add函数用于实现两个数相加,我们可以把Add函数放在test01.c文件中去,然后main函数放在test02.c文件中去,然后调用Add函数。同样我们也使用extern关键词来声明一下Add函数(其实不声明也行但是有警告)

image.png

然后我们使用static关键字修饰一下Add函数,这时在另一个源文件中就算使用关键字extern声明Add函数变量,但Add函数依然不能被调用了:

image.png

总结:一个函数本来是具有外部链接属性的,而一旦static修饰的时候,这个函数的外部链接属性就变成了内部链接属性,其它源文件就不能在使用这个函数了。

15.4、【普及】关键字register---寄存器

image.png

由于CPU处理数据速度非常之快,以至于内存赶不上CPU的速度,所以当处理数据时,内存中的数据向上一级存放,于是就放到在高速缓存、寄存器里面。

那进入整体,在写代码时我们也可以写出寄存器变量:

#include <stdio.h>
int main()
{
    //寄存器变量
    register int num = 3;     //建议把‘3’存放在寄存器里面
    return 0;
}

当把‘3’数据存放在寄存器里面了,这样在读取此数据时会更快,但是需要注意一点,这里只是建议存放,那到底最后是否存放在寄存器里面时编译器说的算。

16、define定义常量和宏

16.1、#define定义常量

这个知识点就是前面讲到常量所涉及到的

#define    MAX 100                       //MAX就是个标识,这里定义的值也可以为字符串,浮点型...
#include <stdio.h>
int main()
{
    printf("%d\n", MAX);
    return 0;
}

 输出:
    100

16.2、#define定义宏

宏是有参数的

上面我们用了Add函数来实现两数之和,这里我们使用宏来代替Add函数实现此功能

#include <stdio.h>

//定义宏
#define Add(x,y) ((x)+(y));

int main()
{
    int a = 10;
    int b = 20;
    int c = Add(a, b);
    printf("%d\n", c);
    return 0;
}

image.png

17、指针

17.1、内存

内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的。

所以为了有效的使用内存,就把内存划分一个个小的内存单元,每个内存单元的大小是1个字节。

为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址

image.png

好的,那现在有个问题:如果有个整形变量数据为“10”,(int a = 10)那int数据类型需要占用4个字节,那就需要向内存申请4个存储单元,那么变量a的内存地址是哪个呢?如下图:

image.png

比如分配的内存地址如下图所示:分配的4个内存空间为:5,6,7,8
image.png

那么此时变量a的内存地址就是第五个地址(首地址)

17.2、如何打印变量内存地址,如何存储内存地址?

1、打印内存地址

&a //取地址操作符

#include <stdio.h>
int main()
{
    int a = 10;
    printf("%p\n", &a);           //打印内存地址
    return 0;
}

2、存储内存地址:

#include <stdio.h>
int main()
{
    int a = 10;
    int* p = &a;            //存储变量a的内存地址
    return 0;
}

17.3、指针变量

重点来了:

int* p = &a;       这个变量p就是指针变量

为什么变量p就被称作为是指针变量呢?

其实很简单,我们都知道每一个内存单元都会有一个编号,又叫做地址,而地址又被称作指针。

image.png

如果变量a的数据类型字符类型,就需要这样写:

#include <stdio.h>
int main()
{
    char a = 10;
    char* p = &a;            //存储变量a的内存地址
    return 0;
}

17.4、*p代表的意思和作用

看一段代码

#include <stdio.h>
int main()
{
    int a = 10;
    int* p = &a;            
    *p = 20;          //解引用操作符,意思就是通过p中存放的地址,找到p所指向的对象,*p就是p指向的对象
    printf("%d\n", a);
    return 0;
}

输出:
    20

p = 20就感觉重新给a = 20赋值一样。(个人分析:\p和直接赋值还是又差别的,*p是找到内存地址进行赋值,这个操作a变量的地址是没有改变的,而重新赋值a变量的内存地址应该是改变了。)

17.5、指针变量的大小

#include <stdio.h>
int main()
{
    printf("%zu\n", sizeof(char*));
    printf("%zu\n", sizeof(int*));
    printf("%zu\n", sizeof(short*));
    printf("%zu\n", sizeof(float*));
    printf("%zu\n", sizeof(double*));
    return 0;
}

//64bit位的电脑上
输出:
    8
    8
    8
    8
    8


//32bit位的电脑上
    4
    4
    4
    4
    4

原因分析:不管是什么类型的指针,都是在创建指针变量。指针变量是用来存放地址的。指针变量的大小取决于一个地址存放的时候需要多大空间,而32位平台下的地址是32个bit位---即4byte,所以指针变量的大小是4个字节,而64位平台下地址是64个bit位---即8byte,所以指针变量的大小是8个字节。

18、结构体

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

比如描述学生,学生包含:名字+年龄+性别+学号这几项信息。因为学生是需要这几个信息同时包含的,属于复杂对象,所以用char,short,int,long...等数据类型不能够单一去表示。所以为了解决相应的问题,C语言就给自定义类型的能力就是结构体。

结构体就是把一些单一类型组合在一起。

那这里只能使用结构体来描述了:

struct Stu
{
    //以下都是结构体成员
    char name [20];
    int age;
    char sex[5];
    char id[15];
};

18.1、用结构体类型来创建一个变量,并打印信息

我们知道创建一个变量很简单,都是数据类型+变量名,比如:

int a 
char t
float dd

那同样上面的struct Stu结构体也是个类型,然后它也能创建变量:struct Stu S,这个S就是变量,

可以发现struct Stu{...}和int的效果一样,只不过int是解决单一变量问题的,而struct Stu解决复杂变量问题的,所以说struct Stu{...}int一样是不占空间的,只有当struct Stu S动作发生,创建出来的S变量才占用一点空间。

那如何使用变量呢?

#include <stdio.h>

struct Stu
{
    char name [20];
    int age;
    char sex[5];
    char id[15];
};

int main()
{
    struct Stu S = 
    {
        "zhangsan",17,"nan","123456789"
    };
    printf("%s %d %s %s\n",S.name,S.age,S.sex,S.id);  //打印的顺序需要相互对应
    return 0;
}

18.2、难度升级一下,问:能不能用指针区实现打印信息的效果?

代码如下:

#include <stdio.h>

struct Stu
{
    char name [20];
    int age;
    char sex[5];
    char id[15];
};

void print(struct Stu* ps)
{
    //形式:结构体对象.结构体成员变量
    printf("%s %d %s %d\n", (*ps).name, (*ps).age, (*ps).sex, (*ps->id));
    printf("%s %d %s %d\n", ps->name, ps->age, ps->sex, ps->id);
    //形式:结构体指针->结构体成员
    //这两者所产生的效果一样。
}

int main()
{
    struct Stu S = 
    {
        "zhangsan",17,"nan","123456789"
    };
    print(&S);
    return 0;
}

就类比前面说到的指针变量那一节,道理一样。

18.3、简单总结

当使用此结构体时:有两种方法,这两种方法是等效的

  • 结构体对象.结构体成员变量,eg:(*ps).name
  • 结构体指针->结构体成员,eg:ps->name

ps->name等价于(*ps).name。

相关文章
|
6月前
|
存储 C语言
C语言的灵魂---指针(基础)
C语言的灵魂---指针(基础)
|
C语言
初始c语言
每周利用空闲时间学习编程,在晚自习进行编程训练,在每周末继续学习编程知识。
45 0
|
算法 C语言
c语言学习第九课---函数2
c语言学习第九课---函数2
60 0
|
C语言 C++
【C语言】初始C语言(1)
【C语言】初始C语言(1)
|
算法 编译器 C语言
从零学习C语言---函数
本章介绍了C语言中函数的使用,函数是C语言中非常要要的一部分。快来学习把。
从零学习C语言---函数
|
存储 C语言 Perl
从学习C语言---指针
本章介绍C语言中的指针,主要内容有:指针和指针类型,野指针,指针运算,指针和数组,二级指针,指针数组。
从学习C语言---指针
|
算法 程序员 C语言
C语言---函数介绍详解
C语言---函数介绍详解
135 0
|
存储 C语言
【C语言】初始C语言(2)
【C语言】初始C语言(2)
|
存储 C语言
初始C语言(2)
初始C语言(2)
85 0
|
C语言 Windows
初始C语言-1.c语言程序的组成
字符又要分字符与字符串,字符是由‘’组成,只占一个字节,而字符串由“”组成,它的长度由其中有多少个字符决定,如“abc”这个字符串的长度为4,为什么呢,因为abc每个字母便占了三个字节,但在计算机内部,系统会自动给字符串结尾加上结束标识符‘/0’这个字符也占了一个字节,故,总长度为4。
197 0
初始C语言-1.c语言程序的组成