C语言学习笔记

一、什么是C语言

C语言是一种强类型语言,变量的类型一旦被确定,在其整个生命周期中是不可被改变的。

int main()//主函数框架
{
    return 0;               //c语言中/**/不能嵌套
}
  • 一个c语言源程序可以由多个源文件组成,但只能有一个主函数
  • 一个源文件可以由多个函数组成
  • 每个语句需要用;结尾

二、基本数据类型

//整型
char;//1字节                   
short;  //2字节               
int;  //4字节                    
long int;//4字节
long long;//8字节
//浮点型
float;//4字节
double;//8字节
long double;//8/12/16
//
bool;//只有真假值 1字节
//
void//无类型     无大小

每一种类型在定义变量时,所分配存储单元个数不同

三、变量、常量、标识符

  • 变量:可读可写;
  • 常量:只可读;
  • 标识符:见名知义
  • 变量:为某一变量分配空间,并赋予变量名(标识符)。该变量与所分配的空间是共存亡的,并且内存位置也不会发生改变。一个变量名在同一个作用域中是不可以被重复定义的。
  • 声明:

1、变量:

变量分为:全局变量(函数之外定义)、局部变量(函数内定义)、块内变量(函数中{}内定义,只在{}有效)

int a=100;//全局
void fun()
{
    int b=100;//局部变量
}
int main()
{
    int b;         //b的可见性只在其函数内所以不冲突
    {
        int c;//块内变量
    }
}

注:当全局变量名与局部变量名相同时,采用就近原则(与上一条语句就近)

int a=100;
void fun()
{
    int b=a;
    int a=50; 
    int c=a;
    int d=::a//使用全局变量
    {
        int c=60;//该语句只在此块内有效
    }
}
//b=100 c=50

2、常量:

常量分为:字面常量、用#defien定义的常量、用const修饰的常量(必须赋初始值)、枚举常量、字符常量和字符串常量

#define MAX 5//宏常量 无类型且不分配空间
enum week={mon=1};
int main()
{
    int a=10;//10为字面常量
    int b=3*MAX;//在预编译过程在MAX会被替换为5,因此宏常量起到的是替换作用。
    //.i文件时该语句 int b=3*5
    const int c=50;//不可写,可读
    const int ARR[10]={1,2,3,4}//数组每个量都变为常量
    enum week d=mon;//若枚举常量未赋值,则从0开始,下一个量累加1(只接收整型)
    return 0;
}

在这里插入图片描述

注:

  • 在编译的过程中才去识别类型
  • .c .i文件都是文本文件
  • .i文件时宏常量以不存在,即已经发生了替换

3、转义字符

  • ‘ ’字符定界符;“ ”字符串定界符;\转义字符(普通->特殊;特殊->普通)
转义字符功能ASCII
\n换行符10
\r回车符13
\t水平制表符9
\0001~3为8进制代表的字符
char ch='\''//单引号
char ch=‘0’(字符0)ASCII 48
char ch=00(false)
char ch=’\0’(空字符)0(false)
char ch=‘a’97
int *p=NULL0(false)
  • 字符串以‘\0’为结束
字符ASCII
‘ ’(空格)32
‘0’48
A65
a97
回车CR
换行LF

四、作用域与生存期

代码区
数据区
堆区
栈区

1、作用域

  1. 作用域是指标识符能够被使用的范围。(此阶段针对编译与链接过程)在可用范围内变量才能被使用。

  2. 全局变量作用域时在定义后,对其一下的语句可见

  3. 局部变量的作用域是在其所定义的函数内可见

    int a=20;//全局
    void fun()
    {
        int b=0;
        int b=a;//a对b是可见的,此语句可执行
        int b=c;//c对b是不可见的,因此无法进行赋值操作
    }
    int c=15;
    int main()
    {
        int b=20;//主函数中的b和fun中的b是不冲突的,局部变量作用域范围不同
    }
    

2、生存期(分配内存空间)

  1. 生存期针对函数的执行过程。不同的作用域下变量的生存期是不同的。(编译链接通过,程序才会被执行)

  2. 生存期是变量在程序执行过程中被创建,并分配相应内存,在程序结束时销毁,内存空间也被回收。

  3. 全局变量存放在数据区,局部变量存放在栈区。

  4. 全局变量是在主函数执行前被存放在数据区,程序结束后释放

  5. 局部变量在函数执行时,被存放在栈帧中,函数结束后被释放。

    void fun()
    {
        int a,b;
    }
    int main()         //在主函数执行到void()函数时,栈才会给a,b分配空间
    {
        void()
    }
    

五、操作数与运算符

1、操作数

int a;
int b=a+10; //a,b,10都为操作数

操作数就时程序中的变量,常量,逻辑值等都可称为操作数(程序操作的数据实体)

2、左右值

在=左边为左值,右边为右值;若为左值则代表该值可读可写;只有右值只可读不可写(该值可以赋给其它变量,但不可被其他变量赋值)。

int a=0;
a=10;
10=a;//不能执行,常量是不可修改的

3、运算符

  1. 对操作数进行相应操作
  2. 分类:一目运算符,二目运算符,三目运算符
  3. 三目运算符解决简单的if语句((a>b)?a:b)
1、前置、后置++
  1. ++在赋值语句中:

    int a=1,b=0;
    b=a++;//后置++是先将a中的值取出放入一个临时的空间中,将a赋值给b,然后将a的值++回写给a(先赋值后++)
    b=++a;//前置++是先将a的值取出放入临时空间,将a进行++后回写给a并赋值给b
    
  2. 在不进行赋值的情况

    for(int i=0;i<100;i++/++i)无区别
    
2、取余操作符
//打印0~100内的偶数
for(int i=0;i<100;i++)
{
    if(i%2==0)
    {
        printf(....)
    }
}
//
n%(10,100,....)其所得结果则是在(0~100~100范围内)
3、赋值运算符

想要改变某个变量的值(除++)一定要进行赋值的操作

六、关键字

  1. 数据类型关键字:

    char int short float double signed unsigned struct union enum typedef sizeof auto static register extern const volatile(多线程使用)

  2. 流程控制关键字:

    if else switch case default for while do return continue break goto

1、sizeof

sizeof:既不是宏也不是函数

int a=10;
int b=sizeof(++a);//sizeof只解析类型大小,++a不进行运算

2、typedef

typedef:给复杂的类型(合法的变量定义)名起一个简单的别名;与其他关键字不能出现在同一表达式;并且需注意作用域问题。(将变量名转化为类型)

typedef char   ch;
typedef int   Array[10];
typedef int *PIN;
struct student
{
	char name[10];
	int age;
}stud,*sp;
//stud=>struct student stud
//*sp=>struct student *sp
typedef struct student
{
	char name[10];
	int age;
}stud, * sp;//stud,*sp被定义为了类型名
int main()
{
	char a = 'A';
	ch b = 'B';
	//ch不再是变量名,变为类型名
    Array br = { 1,5,6,3,7,9 };
    PIN p=NULL;//p为整型指针
    
}

3、static

  1. static关键字可以延长变量的生产周期存放在数据区

  2. static修饰的变量值被初始化一次(作用域问题)

    void fun(int x)
    {
    	int a = 0;
    	int static b = 0;
    	a += x;
    	b += x;
    	printf("%d         %d\n", a, b);
    }
    int main()
    {
    	for (int i = 0; i < 10; i++)
    	{
    		fun(i);
    	}
    	return 0;
    }
    

    在这里插入图片描述

4、extern

  1. extern:用于声明某变量/函数来自同一工程下的其他文件,在此文件下使用。(局部变量与被static所修饰的变量不能使用extern)

七、顺序、循环、选择语句

bool类型

  1. c语言中真有多种(非0),而假只有0
  2. bool类型只有两个值true和false
  3. bool类型一字节0000 000 0最后一位用于表示真假
  4. 关系表达/逻辑表达式运算结果为bool值(a&&b:当a为假时表达式b将不参与运算;a||b a为真时表达式b不参与运算)
    • !虽是单目运算符但没有回写能力与++/–不同,要进行赋值操作。
    • 优先级:= < &&和|| < 关系运算符 <算术运算符 < 非(!)

1、顺序语句

逐行执行

int main()
{
    int a=10;
    int b=a;
    printf(.....);
}

2、选择语句

1、if语句
if(true)
{
    ......
}
else
{
    ......
}

注:if语句中进行条件判断时,若是进行等于判断需用==

2、switch
  1. switch语句是多分支语句

    switch(整形变量表达式)
    {
        case 常量:语句块
            break;
        default:
            break;
    }
    
  2. break用于退出switch。

3、循环语句

1、while循环
while(条件)
{
    .....
}

先判断后执行

2、do…while循环
do
{
    ......
}while(条件)

先执行后判断

3、for循环
  1. for(表达式1;表达式2;表达式3)

    {循环语句}

  2. 表达式一只执行一次,再由表达式2进行判断(true Or false),为真执行表达式三,执行循环体。

4、空语句

  1. 空语句仅由分号组成,不执行任何操作

    int a=10; ;
    
  2. 空语句所带来的问题:若在if、while、for后加上空语句那么其本身的逻辑结构将会被破坏,不会完成其功能。

    if(true) ;
    for(int i=0;i<n;i++) ;
    while(true) ;
    

5、逗号表达式

逗号表达式优先级最低,执行顺序从左向右。

八、函数

  1. 函数是用来完成某种特定且单一的功能;函数分为库函数与自定义函数

    • 库函数:库函数是由系统自身提供的函数。

    • 自定义函数:用户自己编写,由返回类型,函数名,形参列表,函数体构成

      int fun(int a,int b)
      {
          int c=0;
          c=a+b;
          return c;//return 时将c存入临时空间,再将c赋给max,因为作用域的问题c不能在fun与main中同时发挥作用
      }
      int main()
      {
          int a=10,b=20;
          int max=fun(a,b);
      }
      
  2. 形参与实参

    • 形参是再函数被调用时接受传递进来的参数,只有函数被调用时才会被分配空间,函数结束后空间被释放
    • 实参是函数被调用时传递给形参的参数(可为常量、变量、表达式、函数)
    • 函数调用中数据的传递是单向的,可以把实参的值传递给形参,但不能把形参的值反传给实参
    • 将实参值传递给形参也称为实参的入栈(从右向左依次入栈
  3. 值传递与址传递

    void fun(int a, int b)
    {
    	int temp = a;
    	a = b;
    	b = temp;
    }
    int main()
    {
    	int a = 10;
    	int b = 20;
    	fun(a, b);
    	printf("%d %d\n", a, b);
    }
    

    无法实现数值的交换,因为值传递的方向时单向的

    址传递:

    
    void fun_2(int* a, int* b)
    {
    	int temp = *a;
    	*a = *b;
    	*b = temp;
    }
    
    
    void fun(int a, int b)
    {
    	int temp = a;
    	a = b;
    	b = temp;
    }
    int main()
    {
    	int a = 10;
    	int b = 20;
    	fun(a, b);
    	printf("%d %d\n", a, b);
    }
    
  4. void Prin_ar(int br,int n)
    {
        sizeof(br);//==4;当数组进行传参时,数组会退化为指针,因此需将数组的大小一并传入。
    }
    int main()
    {
        int ar[4]={0,1,2,3}
        Prin_arr(ar,4);
    }
    

九、数组

  1. 数组=类型+元素个数

    int arr[10]={};//定义数组时元素个数需是大于零整型常量表达式
    
  2. 数组在内存中是连续存放

  3. 访问数组时,通过数组名和下标访问,下标从0开始。(下标为整形常量或变量)

    for(int i=0;i<5;i++)
    {
        printf("%d",arr[i]);
    }
    printf("%d",arr[3]);
    
  4. 数组的打印方式

    int main()
    {
    	int arr[5] = { 23,34,45,56,67 };
    	int* p = arr;
    	for (int i = 0; i < 5; i++)
    	{
    		printf("%d     %d     %d\n", arr[i], *(arr + i), i[arr]);
    	}//arr[i]=>*(arr+i)       i[arr]=>*(i+arr)
    	return 0;
    }
    

十、指针

  1. 指针变量用于访问存储地址的变量(存放地址);指针就是内存的地址

  2. 无论什么类型的指针,大小都为4字节(32位平台下)。

  3. 指针分为野指针(声明但未初始化)与空指针(初始化为空)

  4. *在不同情况下的含义:

    int a*b;//*表示乘法运算符
    int *a;//*表示声明,声明a为指针变量
    *a=20;//*表示指向,及解引用
    

    在定义指针中*与变量名结合

    int *a,b;//a是指针,b是整形变量
    
  5. int *p=NULL;//声明指针变量p
    int a=10;
    //int *p=&a;
    p=&a;//p存放a的地址
    *p=100;//*p就是a本身,改变*p的值也就是改变a的值(解引用)
    
  6. 指针有两个值一个是其自身的值(存储的地址),还有一个是其指向的值(解引用)

  7. 指针存放的是各种类型变量的首地址(低址值),地址由32位二进制构成,故指针的大小为4字节。

int main()
{
	int arr[5] = { 12,23,34,45,56 };
	int* p = arr;
	int x = 0, y = 0;
	//*与++优先级相同,因此从右向左依次结合 
	x = *++p;
	y = *p;
	printf("%d %d\n", x, y);//23 23
	x = ++ * p;
	y = *p;
	printf("%d %d\n", x, y);//24 24
	x = *p++; 
	y = *p;
	printf("%d %d\n", x, y);// 23 34
	x = (*p)++;
	y = *p;
	printf("%d %d\n", x, y);//34 35
	return 0;
}
  1. 指针的运算

    • 指针可以与整形相加(增加的为所对于变量类型所占字节的大小)

    • 指针之间不可相加(例如日期不可相加)

    • 同类型指针指向连续空间可以相减,减后结果是数据元素的大小(int型结果是整型元素的个数)

      int main()
      {
      	const int n = 5;
      	int arr[n] = { 10,20,30,40,50 };
      	int* pl = &arr[0];
      	int* pf = &arr[4];
      	printf("(pf-pl)=%d\n", (pf - pl));
      	return 0;
      }
      

函数与指针

void fun(int* p)
{
	int a = 200;
	*p = 100;
	p = &a;
}
int main()
{
	int x = 0;
	int* s = &x;
	fun(s);
	printf("%d %d\n", x, *s);
	return 0;
}

数组与指针

  1. 数组的数组名只有在sizeof()中代表整个数组。其他情况下表示的是数组首元素地址。

  2. 某类型指针加一,增加所占类型所占字节

  3. int main()
    {
    	const int n = 5;
    	int arr[n] = { 12,23,34,45,56 };
    	int* p = arr;
    	for (int i = 0; i < n; i++)
    	{
    		//printf("%d  %d\n", arr[i], *(arr + i),i[arr]);
    		//arr[i]==*(arr+i),i[arr]=*(i+arr)
    		printf("%d  %d\n", p[i], *(p + i));
    	}
    	return 0;
    }
    //arr是存储int类型数据的数组,arr是指向数组第一个元素的指针,当arr+1,arr指向数组第二个元素,但int类型的大小为四字节,所以arr+1,就相当于指针跳过了四个字节。
    

    在这里插入图片描述

  4. 传参时数组会被退化为指针(节省时间与空间)

指针变量与const

int const *p;
const int *p;

从右向左,p先于*结合,再与const结合,因此p的值可以修改及可以指向另一个地址,但不能修改*p的值

int *const p;

同上,p先于const结合,其指向的地址不可被修改,但地址所存储的值可以被更改

const int * cosnt p; 

自身值与指向都不可该

无类型指针

  1. void 不可以定义变量,但可以定义指针变量

  2. 无类型指针可以存放任意类型的地址

    int main()
    {
    	const int n = 5;
    	int ar[n] = { 1,2,3,4,5 };
    	void* p = ar;//p可以存放ar的地址
    	//int* ip = p;//但p不可以直接被读取,需进行强转
    	int* ip = (int*)p;
    }
    
  3. ​ 无类型指针大小依旧为4字节,但是无法对无类型指针进行+1,因为内存无法解析无类型的对应的大小(int型加1,地址加4)

十一、二维数组

二维数组在内存中是行优先连续存放的。

  1. int a = 10, b = 20;
    	int* p = nullptr;//*与变量名结合
    	int** s = nullptr;
        s = &p;//s存放指针p的地址
    	*s = &a;//对s解引用,*s即为p本身存放变量a的地址,相当于p=&a;*s=>*&p=>p=&a
    	**s = 100;//对*s解引用,**s及为*p,相当于*p=100;**s=>*p=>a=100
    	return 0;
    

    一级指针存放变量的地址,二级指针存放一级指针的地址

    sint**(type)
    *sint*
    **sint
  2. double a_3 = 0, a_2 = 0, a_1 = 0, a_0 = 0;
    	double* p_3 = &a_3, * p_2 = &a_2, * p_1 = &a_1, * p_0 = &a_0;
    	double** s = &p_0;
    

    s+1增加4字节,因为指针的大小为4字节,而*s+1增加8字节,因为double类型变量占8字节

  3. int *p[4];//开辟4个存储单元的数组,数组中每个元素为整型指针
    int a=4,b=5,c=6,d=7;
    p[4]={&a,&b,&c,&d};
    int (*s)[4];//s是指针存放数组的地址,该数组开辟四个空间,每个元素为整型,及该数组只能存放int [4]类型的数组地址(同类型数组)
    int ar[3];
    
  4. int ar[4];
    ar;//代表首元素的地址
    ar+1;//指向数组的下一元素
    &ar;//代表数组的地址
    &ar+1;//指向下一数组的地址
    //ar与&ar的值是一样的
    
  5. 二维数组与二级指针的关系

    int ar0[4],ar1[4],ar2[4],ar3[4];
    int (*s)[4]={&ar0,&ar1,&ar2,&ar3};
    //通过s改变ar3[3]的值
    *(*(s+3)+3)=10;//*(*(s+3)+3)=10=>*(s[3]+3)=s[3][3]
    

    二维数组是由多个一维数组构成的

    int arr[3][4]是由三个一维数组组成,每个一维数组的大小为4

  6. 定义二维数组时,高位可缺省(arr[][4])

  7.     int ar[4];
    	sizeof(ar);//16
    	int* p = ar;
    	int(*s)[4] = &ar;//相同类型才可存放其数组地址
    	int br[3][4];
    	sizeof(br);//48
    	int(*k)[4] = br;//二维数组的首元素是一维数组的地址
    	int(*l)[3][4] = &br;
    
  8. 二维数组传参

    //void Print_arr_1(int **br, int row, int col)//error二维数组的低位不能缺省
    void Print_arr_2(int(*br)[3], int row, int col)
    {
    	int size = sizeof(*(br[3]));
    	printf("%d", size);//size=4
    }
    int main()
    {
    	int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
    	Print_arr_2(arr, 3, 3);
    	return 0;
    }
    

十二、结构体

  1. 用户设计的数据类型

    struct 结构体名
    {
        成员列表(可以是基本数据类型、指针、数组、其他结构类型)
    }
    
  2. struct student
    {
        char id[20];
        char name[20];//结构体内的变量是不可赋值的,它并没有被分配空间
        int age;
        char sex[4];
    };//;是不可缺少的
    

    结构体是一种数据类型,所设计的结构体是创建变量的模板,不占用内存空间,结构体中声明的变量为属性。

  3. int a;//内置类型可以创建变量
    struct student s1;//结构体类型也可以创建变量
    
  4. 结构体的初始化

    struct student
    {
        char id[20];
        char name[20];
        int age;
        char sex[4];
    };
    int main()
    {
        struct student s1={10001,"qiao",20,"man"};//未初始化的部分会自动补零,与数组相同;初始化的顺序需按照结构体设计的顺序进行初始化
    }
    

    结构体与数组声明时都需要用{},但数组中存放的数据类型是相同的,而结构体不一定

  5. struct studentA
    {
        const char*s_id;
        const char *s_name;
        const char *s_sex;
        int age;
    };
    struct studentB
    {
        char id[20];
        char name[20];
        int age;
        char sex[4];
    };
    int main
    {
        struct studentA s1={10001,"qiao",20,"man"};//sizeof(s1)=16
    struct studentB s2={10001,"qiao",20,"man"};//sizeof(s2)=48
    }
    
  6. 结构体的嵌套

    struct date
    {
        int year;
        int month;
        int day;
    };
    struct student
    {
        
        char id[20];
        char name[20];
        struct date birthdat;
    };
    int main()
    {
        struct student s1={"10001","qiao",2001,12,4};
        struct student s2={"10001","qiao",{2001,12,4}};
    }
    
  7. struct studentB
    {
        char id[20];
        char name[20];
        int age;
        char sex[4];
        struct studentB s;
    };
    struct studentC
    {
        char id[20];
        char name[20];
        int age;
        char sex[4];
        struct studentB *s;
    };
    int main()
    {
        struct studentB s1;
        //s1是不完整类型,sizeof无法计算其大小
        struct studentC s2;//可编译通过,可以定义结构体变量
    }
    
  8. 结构体需用‘.’运算符访问(对象访问),指针用->进行访问

    struct student
    {
        char name[20];
        int age;
        char sex[5];
    };
    int main()
    {
        struct student s_1={"xiaotutou",19,"man"};//s_1结构体变量
        struct student s_2=s_1;//结构体变量可以相互赋值 
        struct student* p=&s_1;//结构体变量
        *p=s_1;
        printf("%s %d %s\n",s_1.name,s_1.age,s_1.sex);
        printf("%s", (*p).name);
    	printf("%s", p->name);//用指针访问时用->(成员指向符)
        return 0;
    }
    
```c
struct student
{
    char name[20];
    int age;
    char sex[5];
};
void print_A(struct studnet s)//s 29字节
{
    printf("%s",s.name);
    printf("%d",s.age);
}
void print_B(struct student *s)  //4字节
{
    printf("%s",s->name);
    printf("%d",s->age);

```
  1. 结构体数组的输出
void Print_stu(struct student stu[], int n)//stu为指针占四字节
{
	assert(stu != NULL);
	for (int i = 0; i < n; i++)
	{
		//printf("%-8s % -8s %-8s %-8d", stu[i].s_no, stu[i].s_name ,stu[i].s_sex, stu[i].age);//-为左对齐
		//printf("%-8s % -8s %-8s %-8d", (*(stu+i)).s_no, (*(stu + i)).s_name, (*(stu + i)).s_sex, (*(stu + i)).age);
		printf("%-8s % -8s %-8s %-8d",stu->s_no,stu->s_name,stu->s_sex,stu->age);
		stu++;
		printf("\n");
	}
}
十一、引用(起别名)
 int main()
 {
     int a=10;
     int b=a;
     int &c=a;//a是c,c也是a,它们的地址也是相同的
     //引用必须初始化,没有空引用
     //int &&b=c;//是不可行的,没有引用的引用
     int &x=c;
 }
void swap(int &a,int &b)
{
    int tem=a;
    a=b;
    b=tem;
}
int main()
{
    int x=10;
    int y=20;
}
a为x的别名,b为y的别名,因此对a,b的交换也就是对x,y的交换。
十二、结构体的大小与联合体
  1. 结构体的大小

    • 结构体变量的首地址,必须是结构体变量中“最大基本数据类型所占字节”的整数倍
    • 结构体变量中的每个成员相对于结构体首地址的偏移量,都是该成员基本数据类型所占字节的整数倍
    • 结构体变量的总大小,为结构体变量中最大基本数据类型所占字节整数倍
    struct sdata
    {
        int year;
        int month;
        int day;
    };
    struct node
    {
        char s_id[10];
        char s-name[8];
        struct sdata birthady;
        double grade;
    };
    

    从0地址开始计算,该结构体大小为40;s_id[10]虽为数组但按其基本数据类型大小计算char的大小为1,data结构体依旧看基本数据类型int为4,此结构体大小为(10+8+2)+(4+4+4)+(8)

  2. 联合体(共用体)

    联合体中,所有成员共享一段空间,其大小为各成员中最长的长度变量。

  3. 哑元结构

    无名的结构体

    struct Node
    {
        char ch;
        union 
        {
            int a;
            float f;
        };
    };//该结构体大小为8
    struct Node
    {
      char ch;
        union Af
        {
          int a;
           float f;
        };
    };//结构体大小为1,此时的联合体为类型,并非属性
    

十三、字符串

  1. 字符串一定是以‘\0’为结束标志

  2. int main()
    {
    	int size = sizeof("copxyw");
    	printf("%d", size);//7
    	const char* sp = "copxyw";
    	char ch_1 = sp[1];//*(sp+1) ch_1==o
    	char ch_2 = "copxyw"[1];//ch_2==o
    	return 0;
    }
    

十四、递归函数

  1. 分治策略:将一个规模较大的问题,划分为规模较小的相同问题

  2. 递归:函数自己调用自己

  3. 递归函数分为递推与回归过程,函数在执行时不断进行递推,直至执行递推的中止条件,再进行回归。再递推过程中栈不断的分配栈帧

  4. 当进行回归时需要用return将值存放在临时空间

十五、位运算

  1. int main()
    {//整型可以进行位运算
    //浮点型和任意指针类型不能进行位运算
    	char a = 12;//0000 1100
    	char b = 27;//0001 1011
    	char c = 0;
    	c = a & b;//(位与)
    	//c = a && b;(逻辑与)结果为1,true
    	c = a | b; //(位或)
    	//c = a || b;简洁或/逻辑或
    	c = a ^ b;//异或
    	c = !a;//逻辑反c为false,a的值不变,没有赋值操作
    	c = ~a;//位反
        a=a>>1;//右移一位
        b=b<<1;//左移一位
    }
    
  2. 当要设置一个整型数据某位的bit位为1时,将改位为1的数和其相或,设为0和其相与

    char a=0;//要将a的第七位bit位设为1;
    //a=0=0000 0000(1字节=8bit)
    a=a|0x80;//1000 0000
    a=a|0x10;//1001 0000
    //将第4位设为0
    a=a&0x80;//1000 0000
    
    

十六、库函数

printf:

int len=printf("a=%d to b=%d\n",a,b);

返回的是格式化后字符串的长度

scanf:

int s=scanf_s("%d %d",&a,&b)

返回的值为正确接收数据的个数

十七、字节对齐问题

1、什么是字节对齐

字节,cup在读写内存时并非逐字节读取,而是以2,4,8的倍数的字节来读写内存;从偶地址读取一个读周期就可读取32 bit,而奇地址需要读两个周期再将数据进行拼接才可得到该32 bit数据,字节对齐的目的是为了提高效率。不同的平台对齐方式也不同,信息传述过程可能出现错乱。

2、指定对齐值

通过预处理指令#pragma pack(n)可改变默认对齐数。n可取1,2,4,8,16

#pragma pack (1) //size=17
#pragma pack (2) //size=18
#pragma pack (4) //size=20
#pragma pack (16) //size=24
//当指定的对齐值比结构体中最大的基本类型大时,以结构体最大的基本类型为对齐值
struct date
{
	char year;
	int month;
	int day;
	double time;
};
int main()
{
	date d;
	int size = sizeof(d);
	printf("%d\n", size);
	return 0;
}

十八、动态内存分配malloc

  • 使用malloc函数手动向堆区申请一块指定大小连续的内存空间;申请完成后需用free()进行释放

    void *malloc (size_t size);//按字节分配大小
    
    int main()
    {
    	int n = 0;
    	int i = 0;
    	int* ip = NULL;
    	scanf_s("%d", &n);
    	ip = (int*)malloc(sizeof(int) * n);
    	if (NULL == ip) exit(1);//需要对ip进行判空
    	for (int i = 0; i < n; i++)
    	{
    		ip[i] = i;
    	}
    	for (int i = 0; i < n; i++)
    	{
    		printf("%2d", ip[i]);
    	}
    	printf("\n");
    	free(ip);//此时ip为空悬指针
    	ip = NULL;//free后ip依旧指向开辟空间的首地址,因此需将ip置为空
    	return 0; 
    }
    
  • 动态分配虽与数组相似,但其也有自身的优点,数组的大小需是常量,并且当其大小超过栈区的内存大小时是不能被运行的。而使用malloc开辟连续的空间其开辟的大小是可以进行自定义,及可使用scanf进行输入。堆区的大小远大于栈区。

  • 动态内存管理的四个函数:malloc、calloc、realloc(用来申请空间)、free(用于释放)

  • 当malloc 申请成功是返回指向新分配内存的指针,为避免内存泄漏,必须使用free()或realloc()再分配返回的指针;申请失败则返回空指针

  • 使用malloc申请的空间有上下越界,用户只可使用上下越界内的空间,上下越界为系统默认值,是不可以被修改的

    int main()
    {
    	int* ip;
    	ip = (int*)malloc(sizeof(int) * 5);
    	if (ip == NULL) exit(1);
    	for (int i = 0; i < 5; i++)
    	{
    		ip[i] = i;
    	}
    	return 0;
    }
    

在这里插入图片描述

  • int main()
    {
    	int* ip = (int*)malloc(sizeof(int));
    	if (NULL == ip) exit(1);//一定要进行判空
    	int* is = (int*)malloc(sizeof(int));
    	if (NULL == is) exit(1);
    	*ip = 200;
    	printf("%d\n", *ip);
    	free(ip);
        //ip=NULL;
    	*is = 1000;
    	printf("%d\n", *ip);
    	*ip = 100;//ip依旧指向malloc所申请空间的首地址
    	printf("%d\n", *ip);
    	free(ip);//同一块空间不可二次free已free的空间
    	return 0;
    }//当free(ip)后未将ip赋值为NULL,将会导致ip依旧指向malloc所开辟空间的首地址,则is开辟空间时可能与ip指向同一块空间,从而导致,ip与is可同时操控此空间,出现程序隐藏的危险
    
  • malloc开辟空间,有头部信息(大小为28字节),其中有用于记录开辟空间大小的标记,因此当使用free时,free可知道应释放多大的空间,实际开辟的空间比真实申请的空间大

  • 内存泄漏:用malloc申请时,丢失了malloc空间的地址,及再未释放的情况下,让指针指向其他地址;只malloc不释放导致堆区空间被用完

    int main()
    {
    	int* ip = (int*)malloc(sizeof(int)*5);
    	if (NULL == ip) exit(1);
    	int* ip = (int*)malloc(sizeof(int) * 10);
    }
    //最终只能释放第二次开辟的40字节,而无法释放第一次所开辟的空间,因为没有记录其地址,导致无法释放,地址丢失
    
  • int *is=(int *)calloc(n,sizeof(int));
    

    将开辟空间中的值赋值为0

    void *my_calloc(int num, int size)
    {
    	void* vp = (void*)malloc(num * size);
    	if (vp != NULL)
    	{
    		memset(vp, num, size);//memset(void *vp,int value,size num)
    	}
    	return vp;
    }
    
  • 堆区与栈区的区别:

    区别
    管理方式由系统自动管理由程序员控制,使用方便,但容易内存泄漏
    生长方向栈向低地址扩张(由下向上)堆向高地址扩张,是不连续的内存区域
    空间大小1M或10M受限于计算机中有效的虚拟内存
    存储内容在一次函数调用后,局部变量先出栈,指令地址出栈,最后栈平衡,由该点继续向下执行堆用于存储生存期与函数无关的数据,由程序员进行具体管理
    分配方式栈可静态或动态分配只能动态开辟,手动释放
    分配效率高;由专门的寄存器,压栈出栈有专门的指令低;由库函数提供,机制复杂
    碎片化问题栈不存在碎片化问题堆区空间被平凡的释放与分配造成堆内存空间不连续,从而导致大量碎片化
    分配后系统响应只要申请内存小于栈剩余内存,系统将为程序提供内存,否则将会报错

二十、文件

  1. stdin //标准输入流
    stdout //标准输出流
    stderr //标准出错流(将错误显示在屏幕上,无缓冲区)
    

注:

  1. #include<assert.h>
    assert(sp!=NULL&&se!=NULL);
    //以下为错误写法
    assert(sp!=NULL)&&assert(se!=NULL);
    if(assert(sp!=NULL));
    

e+1
listnode* p = pl->head;
int i = 1;
while (pos > i)
{
p = p->next;
i++;
}
return p;
}


```c
bool Insert_Node(linklist* pl, int pos, Elemtype val)//插入节点
{
	assert(pl != NULL);
	listnode* p_per = Findpos_per(pl, pos);
	if (p_per == NULL) return NULL;
	listnode* p_insert = buynode();
	p_insert->data = val;
	p_insert->next = p_per->next;
	p_per->next = p_insert;
	pl->cursize += 1;
	return true;
}

[外链图片转存中…(img-H63veAt8-1636506856289)]

bool Erase(linklist* pl, listnode* p_per)
{
	if (p_per == NULL || p_per->next == NULL) return NULL;
	listnode* p_record = p_per->next;
	p_per->next = p_per->next->next;
	free(p_record);
	p_record = NULL;
	pl->cursize -= 1;
	return true;
}
void Erase_Node(linklist* pl, int pos)//删除节点
{
	assert(pl != NULL);
	listnode* p_per = Findpos_per(pl, pos);
	Erase(pl, p_per);
}

[外链图片转存中…(img-cEiBpIl4-1636506856291)]

void Invert_list(linklist* pl)//链表的逆置(头插法)
{
	assert(pl != NULL);
	if (pl->cursize < 2) return;
	listnode* p = pl->head->next;
	listnode* s = NULL;
	pl->head->next = NULL;
	while (p != NULL)
	{
	    s = p;
		p = p->next;
		s->next = pl->head->next;
		pl->head->next = s;
	}
}

二十、文件

  1. stdin //标准输入流
    stdout //标准输出流
    stderr //标准出错流(将错误显示在屏幕上,无缓冲区)
    
/*
stdin 标准输入流
stdout 标准输出流
stderr 标准出错流(将错误显示在屏幕上,无缓冲区)
scanf("%d",&a) stdin
sscanf(buff ,"%d",&a)
fscanf(fp,"%d",&a)
序列化与反序列化???
*/
int main()
{
	//char ch;
	char ch[10];
	FILE* fp = NULL;
	errno_t res = fopen_s(&fp, "2021-7-16.cpp", "r");
	if (fp == NULL)
	{
		printf("fopen file error %d\n", res);
		return 1;
	}
	while (!feof(fp))//未到末尾
	{
		//ch = fgetc(fp);
		fgets(ch, 10, fp);
		printf("%s", ch);
		Sleep(200);
	}
	fclose(fp);
	fp = NULL;
	return 0;
}

int main()
{
	char buffa[128];
	char buffb[128];
	scanf_s("%s", buffa,128);//空格作为输入结束符
	fgets(buffb, 128, stdin);//回车作为输入结束符
	return 0;
}

int main()
{
	char ch = '\0';
	ch = getchar();//stdin
	ch = fgetc(stdin);
}

int main()
{
	int ar[10];
	FILE* fp = NULL;
	errno_t res = fopen_s(&fp, "qch.txt", "r");
	if (fp == NULL)
	{
		printf("fopen file error %d\n", res);
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		fread(ar, sizeof(int), 10, fp);
	}//此时数据在缓冲区中
	fclose(fp);//数据写入文件
	fp = NULL;
	return 0;
}

int main()
{
	//二进制写
	int ar[] = { 10,20,30,40,50,60,70,80,90,100 };
	int size = sizeof(ar) / sizeof(ar[0]);
	FILE* fp = NULL;
	errno_t res = fopen_s(&fp, "qch.txt", "wb");
	if (fp == NULL)
	{
		printf("fopen file error %d\n", res);
		return 1;
	}
	fwrite(ar, sizeof(int), size, fp);
	//此时数据在缓冲区中
	fclose(fp);//数据写入文件
	fp = NULL;
	return 0;
}

int main()//读
{
	int ar[10] ;
	FILE* fp = NULL;
	errno_t res = fopen_s(&fp, "qch.txt", "r");
	if (fp == NULL)
	{
		printf("fopen file error %d\n", res);
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		//fprintf(fp, "%d ", ar[i]);
		//fscanf(stdin,"%d", &ar[i]);
		fscanf_s(fp, "%d", &ar[i]);
	}//此时数据在缓冲区中
	fclose(fp);//数据写入文件
	fp = NULL;
	return 0;
}

int main()//写
{
	int ar[] = { 10,20,30,40,50,60,70,80,90,100 };
	int size = sizeof(ar) / sizeof(ar[0]);
	FILE* fp = NULL;
	errno_t res= fopen_s(&fp,"qch.txt", "w");
	if (fp == NULL)
	{
		printf("fopen file error %d\n", res);
		return 1;
	}
	for (int i = 0; i < size; i++)
	{
		//printf("%d", ar[i]);
		fprintf(fp, "%d ", ar[i]);
	}//此时数据在缓冲区中
	fclose(fp);//数据写入文件
	fp = NULL;
	return 0;

}

int main()
{
	int a = 10, b = 20;
	char buff[30];
	int len = sprintf_s(buff, 30, "a=%d b=%d\n", a, b);//格式化后的字符串存放到buff里
	len = printf("a = % d b = % d\n", a, b);//格式化后的字符串输出到屏幕上
	len = fprintf(stdout, "a= % d b = % d\n", a, b);//显示到标准输出设备上与printf功能相同
	/*float fa = 20.5, fb = 36.3;
	len = sprintf_s(buff, 30, "fa=%f fb=%f\n", fa, fb);*/
	return 0;
}

int main()
{
	//文件操作(1.打开文件并判空 2.读写文件 3.关闭文件)
	int printf(const char* str, ...);//...为可变参数
	int sprintf(char* buff, const char* ptr);
	int fprintf(FILE * fp, const char* ptr);
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值