C语言学习笔记

哔哩哔哩:https://www.bilibili.com/video/BV1oi4y1g7CF?p=2&spm_id_from=pageDriver

C语言基础

什么是C语言

C语言是人与机器交互的语言,是一门面向过程的计算机编程语言。

C语言主函数main基本构成

//包含头文件stdio.h
//standard input/output 标准输入输出
#include <stdio.h>

int main(void)//主程序-程序的入口-main函数有且仅有一个
{
    //程序操作代码
    //函数printf - print function - 打印函数
	printf("hello world!\n");
    
	return 0;
}

//int 是整型的意思
//main前面的int表示main函数调用返回一个整型值

数据类型

数据类型 数据类型名 打印方式 数据长度/字节
int 整型 %d 4
short 短整型 %d 2
long 长整型 %d 4
long long 更长整型 %d 8
char 字符类型 %c 1
float 单精度浮点型 %f 4
double 双精度浮点型 %lf 8

PS:进制

image20210721183019102.png

  1. char 字符数据类型
#include <stdio.h>
int main()
{
	char ch = 'A';//申请一个char类型的空间存放‘A’
	printf("%c,ch");//打印字符A %c-打印单个字符格式的数据
    return 0;
}
  1. int 整型
#include <stdio.h>
int main()
{
	int age = 20;
	printf("%d",age);//打印整形数据 %d
	return 0;
}

short 短整型 = short int

long 长整型 = long int

long long 更长的整型 = long long int

和整型差不多用法只是内存空间的差别

int main()
{
	short age = 20;
	//long age = 20;
	//long long age = 20;
	printf("%d",age);//打印整型都是用%d
	return 0;
}
  1. float 单精度浮点型

单精度浮点型默认显示小数点后6位小数

int main()
{
	float f = 0.5;
	printf("%f",f);
}

image20210721174933765.png

当用%.nf时,小数点.+数字n就是设置打印数值的小数点后n位,且n值小于数值小数位时,自动四舍五入

int main(void)
{
	float f = 5.155;
	printf("%.2f\n", f);//%f-打印float
	return 0;
}

image20210721175833675.png

  1. double 双精度浮点型
//与单精度浮点型float的差别就是 double用%lf打印数据,用法相同
int main(void)
{
	double f = 5.154;
	printf("%.2lf\n",f);
	return 0;
}

PS:其它不同类型数据的打印方式

%p -- 以地址的形式打印

%x -- 打印16进制

%s -- 打印字符串

常量、变量

常量--不会变的量,比如圆周率近似值3.14

变量--会变的量,比如年龄、岁数等,变量又分为局部变量、全局变量。

局部变量和全局变量可以同时存在,建议局部变量和全局变量的名字不要相同,容易产生误会,产生bug。如果存在局部变量和全局变量同名的情况,优先使用局部变量。

#include <stdio.h>
int num = 20;//全局变量-定义在代码块之外的变量
int main()
{
	int num = 10;//局部变量-定义在代码块内的变量
	return 0;
}

image20210721185148131.png

  1. 变量的使用

    变量加 +

    //VS2019出现scanf函数用不了时,需要宏定义下面这句
    //加在源文件的第一行
    //或者把scanf函数改成scanf_s()
    //原因是scanf函数没有做空间越界的判断,所以被认为不安全
    //scanf是C语言提供的
    //scanf_s不是标准C语言提供的,是VS编译器提供的
    #define _CRT_SECURE_NO_WARNINGS 1
    
    #include <stdio.h>
    int main(void)
    {
    	int num1 = 0;
    	int num2 = 0;
    	int sum = 0;//C语言语法规定,变量要定义在当前代码块的最前面
        
    	scanf("%d%d", &num1, &num2);//&为取地址符
    	sum = num1 + num2;
    	printf("sum = %d\n", sum);
    	return 0;
    }
    
  2. 变量的作用域和生命周期

    • 每一对{}大括号就是一个代码块,当变量处于一个{}内时,这个{}大括号就是变量的作用域。

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

int main()
{
  //局部变量的作用域
  {
  	int num = 0;
  	printf("num = %d\n",num);
  }
  return 0;
}
#include <stdio.h>
//全局变量的作用域
//1.局部变量的作用域是变量所在的局部范围。
//2.全局变量的作用域是整个工程。
int global = 2021;

void test()
{
 printf("test() = %d\d",global);
}

int main()
{
 test();
 ptintf("%d\n",global);
 return 0;
}
//test2.c
#define _CRT_SECURE_NO_WARNINGS 1

int g_val = 2021;

int main()
{
 return 0;
}

//test.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
 //为声明的标识符
 //声明:在使用外部全局变量时需要在函数内声明该变量
 extern int g_val;
 printf("g_val = %d\n", g_val);
 return 0;
}
  1. 生命周期 (跟作用域差不多)

    变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段。(只认大括号里的)

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

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

  2. 常量=不变的量

    • 字面常量

      int main()
      {
      	int num = 4;
      	3;//字面常量
          100;//字面常量
          return 0;
      }
      
    • const修饰的常变量

      int main()
      {
          //const = 常属性
          //加了const的变量不可修改
          //num是一个具有常数属性的变量
      	const int num = 6;
      	printf("%d\n",num);
      	num = 8;
          printf("%d\n",num);
      	return 0;
      }
      
      int main()
      {
      	//这里的n是变量不可放入数组
      	//consts int n = 10;也不可以因为这里加了const的n还是一个变量,只是具有常数属性
      	int n = 10;
      	int arr[n] = {0};//数组长度不可定义为变量
      }
      
    • #define定义的标识符常量

      #include <stdio.h>
      
      #define NUM 100
      
      int main()
      {
      	int arr[NUM] = {0};//这里NUM就为100
      	return 0 ;
      }
      
    • 枚举常量enum

      枚举:一一列举

      //性别:男、女、..

      //三原色:红、蓝、绿

      //星期:周一、二、......

      enum Sex
      {
      	MALE,//0
      	FEMALE,//1
      	SECRET//2
      };//枚举常量-是有值的,且默认按顺序为1、2、3、...
      
      enum Color
      {
          RED,
          BLUE,
          GREEN
      };
      
      int main()
      {
      	//enum Sex s = FEMALE;//通过函数定义了的枚举常量类型是可以改的
      	enum Color c = BLUE;
          c = GREEN;
          
      	printf("%d\n",MALE);//0
      	printf("%d\n",FEMALE);//1
          printf("%d\n",SECRET);//2
      	
      	return 0 ;
      }
      

字符串、转义字符、注释

  1. 字符串

    “hello world \n”

    双引号括起来的即为字符串字面值-简称字符串

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

    int main()
    {
    	char arr1[] = "abc";//"abc"--'a','b','c','\0'
    	char arr2[] = {'a','b','c',0};//不加结束符\0打印出乱码
    	
    	printf("%s\n",arr1);
    	printf("%s\n",arr2);
    	
    	return 0 ;
    }
    
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
        char arr1[] = "abc";
        char arr2[] = {'a','b','c'};
        char arr3[] = {'a','b','c','\0'};
        
        printf("%d\n",strlen(arr1));//3
        printf("%d\n",strlen(arr2));//15,随机值
        printf("%d\n",strlen(arr3));//3
        
        return 0;
    }
    

    ASCII码(来自:百度)

请添加链接描述

  1. 转义字符:用\改变某些字符的常规意思

    \n -- 把普通字符n的意思变成换行了

    int main()
    {
    	printf("c:\test\32\test.c");
    	//\t -- 水平制表符
    	//\t 转义字符算一个字符大小
        //\32 -- 32是2个八进制数字
        //32作为8进制代表的那个十进制数字,作为ASCII码值,对应的字符
        //32(8进制) --> 10进制 26 --> 作为ASCII码代表的字符
    	return 0;
    }
    
    int main()
    {
    	//当要打印一串“are you ok??”时,得加转义字符\把??单独转义
    	printf("(are you ok\?\?)\n");
    }
    
    转义字符(\后加任何东西都算一个字节大小) 释义
    ? 在书写连续多个问号时使用,防止他们被解析成三字母词
    \ ' 用于表示字符常量‘
    \ " 用于表示一个字符串内部的双引号
    \\ 用于表示一个反斜杠,防止它被解释为一个转义序列符
    \a 警告字符,蜂鸣
    \b 退格符
    \f 禁止符
    \n 换行
    \r 回车
    \t 水平制表符
    \v 垂直制表符
    \ddd ddd表示1-3个八进制的数字。如:\130 X
    \xdd dd表示2个十六进制数字。如:\x30 0
    //   \xdd
    //  61(十六进制) --> 97(十进制)  'a'
    int main()
    {
    	printf("%c\n",'\x61');
    	return 0 ;
    }
    
  2. 注释

    • 代码中有不需要的代码可以直接删除,也可以注释掉
    • 代码中有些代码比较难懂,可以加一下注释文字

    比如://注释掉单行代码

    ​ /**/ 注释掉多行代码

    C语言风格的注释 /* xxxxxxxxx */

    ​ 缺陷:不支持嵌套注释

    C++风格的注释 /* xxxxxxxx */

    ​ 可以注释一行也可以注释多行

选择语句

//if语句
int main()
{
    int coding = 0;
    printf("你会去敲代码吗?(选择1 or 0):>\n");
    scanf("%d",&coding);
    if(coding == 1)
    {
        printf("坚持,你会有好offer!");
    }
    else
    {
        printf("放弃,功亏一篑...");
    }
    return 0 ;
}

循环语句

//while语句
int main()
{
    int line = 0;
    printf("写一行代码 %d\n",line);
    
    while(line<20000)
    {
        line++;
        printf("写满20000行代码  %d\n",line);
    }
    if(line<20000)
    {
        printf("keep going!");
    }
    if(line>=20000)
    {
        printf("successed~");
    }
    return 0 ;
}

函数

函数包括:自定义函数,库函数等等

//自定义函数
int Add(int x,int y)
{
    int z ;
    z = x+y ;
    return z;//返回值z为int整型,所以函数应该是int Add()
}

int main()
{
	int num1 = 10;
    int num2 = 20;
    int sum = 0;
    int a = 100;
    int b = 200;
    
    //sum = num1 + num2;
    sum = Add(num1,num2);
    //sum = a+b;
    sum = Add(a,b);
    printf("sum = %d\n",sum);
    
    return 0 ;
}

数组

int main()
{
    //int a = 1;
    //int b = 2;
    //int c = 3;
    //int d = 4;
    //int e = 5;
    
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    //数组下标从0开始,即arr[0] = 1;
    
    char ch[20];
    
    while(1)
    {
        printf("%d\n",arr[i]);
        i++;
    }
    return 0 ;
}

数组内一个元素大小为4字节

​ int arr[] = {1,2,3,4,5,6};

​ 该数组内有6个元素,即数组大小为24字节

​ sizeof(arr) = 24;

PS: sizeof 和strlen 的用法区别

​ sizeof 和 strlen 这两个函数都可以用来计算大小,但又存在差别,sizeof计算该量的占用内存的大小,strlen计算字符串的长度。sizeof可以用于计算数组大小、字符串大小,而strlen不能用于计算数组大小。

当这两个函数同时用于计算字符串时,由于sizeof计算字符串时,字符串后会加上结束符\0所以相同的字符数量的字符串,用sizeof计算的会多1。

image20210726205151622.png

操作符

  • 算数操作符

  • 移位操作符

    << 左移操作符 : 乘2 >>右移操作符:除2

  • 位操作符

    & 按位与 (×)全1才1

    | 按位或 (+)有1就1

    ^ 按位异或 同0异1

  • 赋值操作符

    =

    +=

    -=

    *=

    /=

    &=

    |=

    ^=

    单目操作符

    ! 逻辑反操作

    -:负值

    +:正值(可省略)

    &:取地址

    sizeof:操作数的类型长度、变量长度(以字节为单位)

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

    - -:前置- -:先--再赋值后置- -:先赋值再--

    + +:前置++:先++再使用后置++:先使用再++(使用==赋值)

    //前置++ :先赋值再自增++
    //a先赋值给b -- b = 10
    //a再自增 -- a = a+1 -- a = 11;
    int main()
    {
    	int a = 10;
    	int b = 0;
    	b = ++a;
        
        printf("a = %d\n",a);
        printf("b = %d\n",b);
        
        return 0 ;
    }
    

image20210726211447454.png

//后置++ : 先自增再赋值
//a先自增 -- a = a+1 -- a = 11
//a再赋值给b -- b = 11
int main()
{
	int a = 10;
	int b = 0;
	b = a++;
    
    printf("a = %d\n",a);
    printf("b = %d\n",b);
    
    return 0 ;
}

image20210726211743969.png

*:间接访问操作符

(类型):强制类型转换

int a = (int)3.14 ;

双目操作符

<u>非0即为真</u>

  1. && 逻辑与

    全真才真

  2. || 逻辑或

    有真即真

三目操作符

  1. 条件操作符

    • exp1?exp2:exp3

      若exp1为真 -- 执行exp2

      若exp1为假 -- 执行exp3

    int main()
    {
        int a = 10;
        int b = 20;
        int max = 0;
        
        max = (a>b?a:b);
        //等同于下列代码
        if(a>b) max=a;
        else max=b;
        
        return 0 ;
    }
    
  2. 逗号表达式

    exp1,exp2,exp3,···expN

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

    [] ( ) . ->

关键字

typedef 类型定义/类型重定义

typedef unsigned int u_int;
//unsigned int = u_int;
u_int num1 = 20;

​ static :

​ 修饰局部变量,局部变量的生命周期变长;

​ 修饰全局变量,改变了变量的作用域,让静态的全局变量只能在自己所在的源文件内部使用,出了源文件就没办法使用了;

​ 修饰函数,(也是改变了函数的作用域)改变了函数的链接属性。普通函数是具有外部链接属性的,当用static修饰后变成了内部链接属性,即用<u>static修饰的函数不能被外部函数调用</u>。

//add.c
static int Add(int x,int y)//被static修饰后的函数不能被外部调用
{
    int z;
    z = x+y;
    return z; 
}

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

//运行结果出现错误
void test()
{
	static a = 1;//a是一个静态的局部变量
    a++;
    printf("a = %d\n",a);
}

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

//结果输出:
a = 2;
a = 3;
a = 4;
a = 5;
a = 6;

#define

#define MAX 100 //定义标识符常量

#define

#include <stdio.h>

#define MAX(X,Y) X>Y>X:Y

//函数
int Max(int x,int y)
{
    if(x>y)  return x;
    else  return y;
}

int main()
{
    int a = 10;
    int b = 20;
    
    //函数
    int max = Max(a,b);
    printf("Max = %d\n",max);
    
    //定义宏的方式
    max = MAX(a,b);
    printf("MAX = %d\n",max);
    
    return 0 ;
}

指针

内存

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

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

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

内存原理

地址 -> 空间(房间)

32位处理器可以产生2的32次方个内存空间,每个内存空间大小为1个字节(8bit)

int main()
{
	int a = 10; //申请4个字节
    
    //& 取地址符
    //*p = a;
    //p = &a;
    int* p = &a ;// 取出a的地址
    printf("%p\n",&a);
    printf("%p\n",p);
    //指针是一种变量,用来存放地址
    //printf("%p\n",&a);//运行出来的地址是16进制的
    
    *p = 20; // * - 解引用操作符,通过*p找到所指的对象
        
    return 0 ;
}
int a = 10;  //a是一份内存空间,里面存放的值是10

int* p = &a; //p是a的空间编号

* p = 20;  //通过*p来改变空间里存放的a的值

image20210727181657322.png

int main()
{
	char ch = 'w';
    char* pc = &ch;
    *pc = 'a';
    printf("%c\n",ch);
    
    return 0 ;
}

指针变量的大小

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个字节。(VS2019可通过新建配置管理器修改成64位平台)

结构体

结构体

描述复杂对象 -- 结构体 :是我们创造出来的一种类型

个人信息结构体:名字+身高+年龄+身份证号

书本信息结构体:书名+作者+出版社+定价+书本号

//创建一个结构体类型
struct Book
{
	char name[20];//书名
    short price;//55
};

int main()
{
    //利用结构体类型创建一个该类型的结构体变量
    struct Book b1 = {"C语言程序设计",55};
    printf("书名:《%s》\n",b1.name);
    printf("价格:%d元\n",b1.price);
    
    //修改书名,书名的定义是数组,
    //所以不能用该变量的方法来改结构体中的数组信息
    //strcpy-string copy - 字符串拷贝-库函数 - 包含头文件string.h
    strcpy(b1.name,"C++");
    printf("书名 : 《%s》\n",b1.name);
    
    //修改价格
    b1.price = 50 ;
    printf("价格:%d\n",b1.price);
    
    
    struct Book* pb = &b1;
    //利用pb打印出书名和价格
    // . 操作符用在 : 结构体变量.成员
    // -> 箭号操作符 : 结构体指针->成员
    printf("书名:《%s》\n",(*pb).name);
    printf("价格:%d元\n",(*pb).price);
    
    //利用pb打印出书名和价格
    printf("书名 :《%s》\n",pb->name);
    printf("价格 :%d元\n",pb->price);
    
    return 0 ;
}