一定不能错过的c语言基础(超级详细)

一.命名

1.由下划线,数字,英文字母组成;且首字符不能是数字。
2.尽量做到见名知意
3.严格区分大小写斜体样式
4.在C89中,限制标识符的长度为8,长度大于8,但是在C99中对长度没有任何限制
5.尽量 不要同时用零和O或者l和1以下是它们在编译器的样子,十分难以区分,会破坏代码的可读性

在这里插入图片描述

在这里插入图片描述

二.变量和常量

定义变量的格式 数据类型 变量名
定义:分配一块内存空间并为它起上名字,从此这块空间和这个名字同生死
声名:提前告诉编译器这个名字已经预定好了,其它变量不能 用它来做为名字

常量分为

1.字面常量
1,1.0形如这样的叫字面常量
2.#define定义的宏常量
形如 #define PI 3.14这样的,它的意思是在预编译时会把PI替换成3.14
3.const定义的常 量
在c语言中,它更切近于“变”这个词,而在c++中它更切近于常这个词。如下,在.c文件中它会报错,而在.cpp文件中它不会报错

在这里插入图片描述
在这里插入图片描述
const int a;
int const a;
这两个是等价的;

4.字符常量 :字符串常量** 字符常量的定界符为’’ 字符串常量的定界符为“”
5. 枚举标识符

              
enum week {

	Mon = 1,
	Tue = 2,
	Wed = 3,
	Thu = 4,
	Fri = 5,
	Sat = 6,
	sun = 7,


};
int main()
{
	enum week wk;
	wk = Mon;
	
	return 0;
}

`
(如上面的Mon)不能赋值float类型,可以赋值为负数,不同的枚举标识符也可以赋相同的值,如果没有赋值的话,那么第一个枚举标识符的默认的值为0,之后的枚举标识符的值依次加1.

6.转移字符

常见的转义字符ASCLL
\a 警告符7
\b 退格符,将位置移到前一列8
\f 换页符,将位置移到下一页开头12
\n 换行符 ,将位置移到下一行开头10
\r 回车符,将位置移到本行开头13
\t 水平制表符,将光标往后移动一个TAB的长度9
\v 垂直制表符11
\ 代表一个\符号92
’ 代表一个’符号39
" 代表一个"符号34
? 代表一个?符号63
\0 空字符0
\000 1到3位8进制的数字3位八进制数
\xhh 1到2位16进制的数字2位二进制数

注意点


 int main()
 {
	 char a ='\''; //存放单引号的值时,必须先转义
	 char b = '"';
	 char c = '\"'; // 存放双引号时,可以加\也可以不加
	 char d [] = {"\"c\"语言"};// "c"语言 这样的情况,就必须加\
	 return 0;
 }

int main()
{
	char a = 0;      //0
	char b = '\0';  //空字符 0
	char c = ' ';  //空格字符 0
	char d = '0'; // 48 
}

一道笔试题

int main()
{
	char stra[] = {"hello\0data"};
	char strb[] = {"hello\\0data"};
	int size = sizeof(stra);
	int len = strlen(stra);
 
	printf("%d",size); //11   h,e,l,l,o,\0,d,a,t,a,\0依次被存入数组的空间
	printf("%d",len); //5 
	printf("%s",stra); //hello  \0是字符串结束的标志
	printf("%s",strb); //hello\0data   h,e,l,l,o,\,0,d,a,t,a(\\首先被转义成\)

}

如果要存路径,一定要注意

 int main()
 {
   char path1 [] = {"c:\\c语言"};//记住是\\
   char path2 [] = {"c:/c语言"};//防止忘记起见,在window 和 linux下都可以这样写
 }

一道有需要注意的题

 int main()
 {
   char a = {"c\141语言"};
   printf("%s",a); // 打印结果  ca语言
   char b = {"c\149语言"};
   printf("%s",b);//因为8进制每位最大为8,所以它看成\14(两位8进制的数),并打印它对应的符号,而9则看成字符跟在后面打印出来
 }
 char c = {"c\666语言"}printf("%s",c);//无法通过编译,因为\666所对应的10进制数已经超过ACSLL码的范围

c++中变量的定义

int a =0;
int a =(int)0;
int a(0);

这些都是一样的

三 .运算

1.操作数
操作数可以为变量,也可以为常量,
2.运算符
对数据进行操作的符号
可分为
2.1:单目运算符
具有回写能力
++,- -
a++; 后自增 a–;后自减(先赋值,再运算)
++a;先自增 --a;先自减(先运算,再赋值)
常量不能进行自增自减操作
a=b++;

先把b的值放在临时空间中(寄存器),然后把这个值赋值给a,然后这个值再加1,然后回写给b

a=++b;
先把b的值放在临时空间中(寄存器),然后这个值再加1,然后回写给b,然后这个值赋值给a.,

int main()
{
  float a =3.25;
  a++;//a的值为4.25
  a--;//a的值为2.25
  //对float/double自增,自减时,只对整数部分做运算
}
 int main()
 {
   int i=0;
   ++i=100;
   i++=100//错误  先把i放到临时存储空间,然后把100赋值给这个临时量,因为常量不能被赋值,所以错误
 
 }

2.2.双目运算符
特殊 ‘%’ c ,c++,java中取余,python中取模
取余运算在计算时向0方向舍弃小数位(遵循尽可能让商大)

5%-3=2
-5%3=-2;
取模运算在计算时向负无穷方向舍弃小数位(遵循尽可能让商小)
5%-3=-1;
-5%3=1;
在c语言中取模的数只能为整型
2.3.三目运算符
A?B:C; (if ,else if,if 的简写)
3.左值 "="左边的称为左值
左值,既可以赋值,又可以取值
5.右值 "="右边的称为右值
右值,只可以取值
2.4.逗号表达式

int main()
{
  int a=1,b=2,c=3,d=0;
  d=a+1,b+2,c+3;//d为2
  d=(a+1,b+2,c+3);//d为6
  
}
int main()
{
  int n;
  while(scanf("%d",&n),n>0)//,逗号表达式的用法
  {
    代码块;
  }
}

等号的优先级高于逗号表达式
3.强制类型转化
3.1.基本数据类型的强制转化
当把一个大字节的变量强制转化给一个小字节的变量时,从大字节的低位截断

3.2.地址的强制转化
把地址以不同的类型进行解析
c++中强制转化
static_cast
const_cast
reinterpret_cast
dynamic_cast
为了让使用地址强制转化时更安全,同时也为了提醒使用者,现在在进行地址的强制类型转化,比较危险,要注意
一道面试题

int main(){
	const int c = 10;
	int d = 0;
	int* p = (int *)&c;
	*p = 100;
	d = c;
	printf("%d\n", c);10
	printf("%d\n", d);100
	printf("%d\n", *p);100
}

因为c++中const 修饰的视为常量,所以在编译的时候就把代码中的c全部替换成10
运算符

  1. : !
  2. : |
  3. : &
  4. : ^
  5. : <<
  6. : >>
  7. : ^
    1:结合律 (a^b) ^c =a ^(b ^ c)
    2:交换律 a ^ b =b ^a
    3:归零律 a ^ a =0
    4: 恒等律 a ^0 =a
    5:自反律 a ^ b ^a =b
    :<< 左移时,无论正负,低位都补0
    :>>右移时,高位补符号位

    计算数字中1的个数
int Getnumber3(int val) {
	int number = 0;
	int n = sizeof(val) * 2;
	for (int i = 0; i < n; i++) {
		number += "\0\1\1\2\1\2\2\3\1\2\2\0\2\3\3\4"[val & 0x0f];
		val = val >> 4;
	}
	return number;
}
int Getnumber2(int val) {
	int number = 0;
	while (val != 0) {
		val  &= (val - 1);
		number++;
	}
	return number;
}
int Getnumber(int val) {
	unsigned int t = val;
	int number=0;
	while (t != 0) {
		if (t & 0x001 ) {
			
			number++;
		}
		t = t >> 1;
	}
	return number;
}

四.关键字

1.sizeof
计算变量或类型的大小,(是在预处理时就进行的)

int main()
{
 int a[10] = { 1,2,3 };
	int b = 10;
	int* p;
	int size1 = sizeof(a);
	int size2 = sizeof(p);
	int size3 = sizeof(++b);
	printf("%d\n", size1);//40  4*length
	printf("%d\n", size2); //4(32位操作系统),8(64位操作系统)
	printf("%d\n", size3);//4
	printf("%d\n", b);//10 sizeof为关键字,在计算的时候,它不关心括号里面具体的运算,只是关心它的类型,


     
  
}
int main()
{
  char a []={"cyuyan"};
  int l1 = strlen(a);
  int 12 = sizeof(a);
  int l3 = sizeof("hellocyuyan");
  printf("%d",l1);//6
  printf("%d",l2);//7后面还有'\0'
  printf("%d",l3);//12 后面还有'\0'
}

2.typedef 在c语言中为类型起一个别名,是存储型关键字,与auto,extern,mutable,static,register等不能出现在一个表达式中
auto:(c语言及,c++98,用于声名变量是自动类型的变量,具有自动存储期,这种变量在进入变声名该变量的程序块时才被建立,退出则会被撤销,在c++11时,它被用做类型推断,即定义变量时不需要声名类型,类型则会被推断出来)

 int main()
 {
    struct stuent
    {
      char [20] name;
      int age;
     
    }Student,*PStudent;//一个struct student 类型的变量Student,一个指向struct student类型的指针PSstudent。
    typedef struct student
    {
      char [20] name;
      int age;
     
    }Student,*PStudent;//一个包含char [20]name,int age 的类型,一个指向struct student的指针类型
 }
int main()
{
  typedef int array [10];
  array a = {1,2,3};//a为长度为10的数组
}

3.static
修饰变量或函数(存放在数据区)
局部变量:当这个函数被调用时,这个函数中的静态变量被初始化,当下一次调用这个函数的时候,这个static变量再也不会被初始化,仍然保持上次的结果
全局变量:只能在当前c文件中使用

当static修饰局部变量时,当这个局部变量未初始化,那么它的默认值为0(而全局变量定义未初始化时,它的默认值也为0) (未初始化的全局变量存放在数据区的bass)



 int main()
 {
    void a()
    {
      static int a;
    }
    void b()
    {
      static int a;
    }
    
 }

这两个并不冲突,尽管它们都存放在数据区,但是它们各自的作用域只在它们各自的函数中,因为在数据区存放的时候,它们会被标记,那个b是哪个函数里面的


  void fun(int x)
  {
   int a = 0;
   static int b = x;
   a+=1;
   b+=1;
   printf("%d %d",a,b);
  
  }
  int main()
  {
    int n=5;
    for(int i=n;i>0;i--)
    {
      fun(i);
    }
    
  }

在c语言中比不能通过编译(不能把变量给static 修饰的变量赋值),不过这个经常会当作c++的题来作为笔试题,在链接的时候,先在数据区给b开辟空间,并赋初始值0,并给它一个标志位1,当第一次调用fun函数,执行到static int b =x,先检查标志位是否为1,为1,然后把x赋值给b,然后把标记域改为0,等到下一次调用fun函数,执行到这一语句,当发现标记域为0时,对b不进行初始化

int mian()
{
  int static a = 10,b = 10;
  const static c = 10,d = 10;//a,b,c,d都为常量
  int e = 10,static f=10;//不能和const一样,会报错
  
}

4. extern.
可以用来修饰变量或者函数
1.修饰同一个工程中的其它.c文件中的变量/函数
告诉编译器先进行编译生成.obj.文件,等链接时再去获取它具体的值/内容
2.修饰同一个.c文件中的变量

int main()
{
   extern int a;
   void n(int m,int n)
   {
     a=m+n;
   }
   int a=1;
}

当在一个工程下,其中的一个.c文件中使用static修饰了一个全局变量,就算在其它.c文件中对这个变量使用了extern关键字,也不能在其它文件中使用

void n()
  {
     int a;
  }
int main()
{
  extern int a;
  
}

不能这样使用,extern中针对于全局变量或者函数

5.const
5.1 const修饰变量
可以修饰全局变量,也可以修饰局部变量(修饰之后,变量只可读,不可写)
int const a = 1;
const int a = 1;

int main()
{
 const int a = {1,2,3};//也可以修饰数组
 
}
 int main()
 {
   const int a;//c编译中不会报错,c++编译中会报错
**加粗样式**   int b = a;//在c中无法通过编译
 }

所以用const修饰变量时一定要首先初始化

int main()
{
  int const a = 10,b = 10;
  const int c = 10,d = 10;//a,b,c,d都为const修饰的常变量(const是与类型绑定在一起的)
  int e = 10,const f=10;//此时f不为常变量(const左右没有变量,被自动忽略)
  
}

5.2 const修饰指针**

1. int * const p;

int main()
{
   int a =10;
  int * const p = &a;
   *p = 20;
   int b;
   p = &b;//ERROR 
  
  
 
}

只能修改p所指向物的值,不能修改p的指向物,cost这样修饰时,必须在定义时就赋值

2. int const * p; 等于const int *p;

int main()
{
 int a = 10;
 int b = 20;
 int const *p = &a;
 *p=100;//ERROR
 P = &b;//
 
}

能修改p所指向物,而不能修改p所指向物的值
3. const int * cnost p;

 int main()
{
	int a = 10;
	int b = 20;
	 const int * const p = &a;
	*p = 100;//ERROR
	 p = &b;//ERROR

}

既不能修改p的指向物,也不能修改p所指向的值

const和define在作用上很像。但也有很多不同
1.#define是预编译指令,而const是变量的定义,
2.define是在预处理时展开,而const是在编译时处理。
3. const定义的是一个变量,而define定义的是一个常量,
4. define定义的宏在预编译之后就不存在了,也没有占用空间,而const修饰的常变量本质是一个变量,它具有类型,占有空间,可以说常变量是有名字的常量,有名字是为了在程序中便于引用,从使用者的角度,它除了不能作为数组的长度(c语言中),它具有宏的优点,
所以在define和const都能使用时,首先考虑const,define定义的为常量,const定义的是变量,有数据类型,编译器会对它进行安全检查

#define SUM(x,y) x*y
 int main()
 {
  int a=5,b=4;
  printf("%d"SUM(a+2,b+2));//结果为(a+2*b+2) 15
 }

边界问题

6.register
建议将变量存在cpu 的寄存器中以提高访问速度,以提高访问速度
1.不能定义过多,因为cpu中只有4个通用寄存区,而且有些寄存器只能接受特定的类型,如指针类型或者浮点类型,而且能否存放在寄存器中,还看你的编译器,有些寄存器变量会被直接忽略
2.寄存器不能用’&'符取地址,因为它存放在寄存器中
3.只有局部变量和形参才能定义为寄存器变量,全局变量不行(在程序执行时一直占用cpu的寄存器资源
4.局部静态变量不能定义为寄存器变量

五.选择结构

boolen类型
在c99引入了要想使用的话,必须引入<stdbool.h>
也可以自己定义

 typedef char bool
 #define ture 1
 #define false 0
 int main()
 {
    bool x=1;
    printf("%d",x);//1
    x=x-1;
    printf("%d",x);//0
    x=x-1;
    printf("%d",x);//1
   
 }

只有0,1值
1.if else if , else
2.switch
1 使用形式
if,else if,else只能控制离它最近的一条语句,如果想控制多个语句,必须加花括号。
else if,else遵循最近原则,向上找离它最近的If/if ,else if 进行匹配

使用这个语句,可以按照自己逻辑,不必约束

 if(条件)
   {
   语句;
   }
   /--------------------------------------------/
 
   if(条件)
   {
    语句;
   }else if(条件)
   {
    语句;
   }
   /---------------------------------------------/
     if(条件)
   {
    语句;
   }else if(条件)
   {
    语句;
   }else if(条件)
   {
   语句;
   }
  /------------------------------------------------/
      if(条件)
   {
    语句;
   }else if(条件)
   {
    语句;
   }else
   {
    语句;
   }
   /--------------------------------------------------/
   if(条件)
   {
      if(条件)
      {
       语句;
      }else if
     {
     语句:
     }
   }else if(条件)
   {
   语句;
   }

2. swith

1.只能对基本数据类型中的整型使用switch,这些类型包括int char等
2.switch()的参数类型不能是浮点型,字符串
3.case标签必须是常量表达式
4.case标签不能重复

    case 4+2://ture
    case 'A'//ture
    case 'A'+2: //ture
    case 6.5  //false

六.循环结构

1. for( ; ; ;)

这三种等价

/--------------------------------------------------/
int mian()
{ 
 int sum = 0;
 for(int i=1;i<=100;i++)
 {
   sum+=i;
 }
}
/---------------------------------------------------/
int mian()
{ 
 int sum = 0;
 int i=1;
 for(;i<=100;i++)
 {
   sum+=i;
 }
}
/---------------------------------------------------/
int mian()
{ 
 int sum = 0;
 int i=1;
 for(;i<=100;)
 {
   sum+=i;
   i++;
 }
}
/---------------------------------------------------/

int mian()
{ 
 int sum = 0;
 int i=1;
 for(;;)
 {
   sum+=i;
   i++;
 }
}//死循环
/---------------------------------------------------/
int mian()
{ 
 int sum = 0;
 int i=1;
 for(;;)
 {
   sum+=i;
   i++;
   if(i>=100)
   break;//这个与上面的三种等价
 }
}

int main()
{
  for(int i=0;i<5;i++);
  for(int i=0;i<4;i++);//在有的编译器中,此时i的作用域只在这个for循环中,而有的编译器则不是这样
}

2.while()
{}

2.do
{}while
;先执行一遍函数体,再进行判断
使用这些的时间要小心在后面加一个’;',这样会执行这个空语句,而不去执行函数体里面的内容

跳转语句:break,continue,goto,return,
1.break:用在switch和循环中,每次只能跳一层(即只能跳出离它最近的循环结构)
2.continue
for(表达式1;表达式2;表达式3)
{
if(条件)
{
continue;//跳往表达式3
}
}
while(表达式1)
{
continue;//跳往表达式1
}
do{
continue;//跳往表达式1
}while(表达式1);

在while和do while中使用continue中要小心,因为它们里面很容易出现死循环

3.goto 语句(
尽量不要使用)

int  main()
{
  for(int i=0;i<=10;i++)
   for(int j=0;j<=10;j++)
   {
      if(i==5&&j==5)
      goto flag;
   }
   flag ://如果满足条件,则直接跳转到这里
}
int  main()
{
  goto flag;
  for(int i=0;i<=10;i++)
   for(int j=0;j<=10;j++)
   {
     flag :
     printf("flag");
    
   }
  
}//不允许直接从循环外往循环里面跳
int main()
{
  int add(int x,int y)
  {
  flag:
    return x+y;
  }
  int main()
  {
    goto flag;
  }
}//也不允许从一个函数条跳往另外一个函数

七.作用域

1.局部变量
局部变量的作用域为什么只在该函数中:

代码以二进制形式存储在代码区,当执行到一个函数时,在栈区为这个函数申请栈帧,而函数中的形参和变量在这个栈帧中分配空间,而为函数分配的这些空间是相互独立的,所以这个这些变量也被称为局部变量,而它们的作用域也仅仅在该空间,也就是该函数内。

2.全局变量:全局变量储存在数据区(初始化的全局变量存放在data区,未初始化的全局变量存放在bass区,不分配空间),数据区只有在程序全部执行完之后才会被释放。所以它们的作用为整个程序。

全局变量定义后,如果不赋初始值的话,则默认值为0 这也是为什么它可以不分配空间

    int add(int a,int b)
    {
      int c = a+b+d;//此时无法通过编译,尽管d为全局变量,储存在数据区,但是代码是储存在代码区从上往下依次执行的,当执行道这一语句时,此时还没有把d存储在数据区,所以说对于编译器来说,d还未定义
      return c;
    }
    int d;
    int main()
    {
      return 0;
    }

3.静态变量: 当static修饰局部变量时,它只能在定义的该函数内使用。
当static修饰全局变量时,它的作用域为整个程序

八.数组

一.一维数组

1. 数组:
1.数组的元素可以是任意类型,
1.1 .数组的特征:
1.1.1.元素类型
1.1.2.元素个数
描述一个数组:一个Type类型的长度为N的数组
2.定义: 类型 名字 [N] N必须为大于0的常量

int main()
{
  int n;
  int a[n];
  scanf("%d",&n);
}

2.计算数组长度
int length = sizeof(arr) / sizeof(arr[0]);
为什么数组定义的时候只能是常量?
因为如果是变量的话,它的长度是未知的,而在windows中栈的空间只有1M,为了防止在程序运行时从终端接受一个值,直接把栈的空间占用完,所以不允许数组的长度为变量
数组名 :被看作数组第一个元素的地址(sizeof中除外),在表达式中被自动转化成指向第一个元素的指针常量

3.arry[i] = * (arry+i)
所以arr[i] =i[arr];

int main()
{ 
  int arry[5] = {12345}for(int i=0;i<5;i++)
  { 
    printf("%d",a[i]);
    printf("%d",i[a]);//这两个等价
  }
}

4.数组做形参时会退化成指针

void print_Arry(int br[5],int n)
{
 int size = sizeof(br);//为4
}
void int br[3][4]//退化成 int (*br)[4]
{
}
//
int main()
{
 int arr[5] = {1,2,3,4,5};
 print_Arry(arr,5);
 
}

此时并不会在printf_Arry这个栈帧中创建一个长度为5的数组,而是创建一个指针变量存储arr首元素的地址

二.维数组
可以理解成一个一维数组的每个单元格里面又存储了一个数组
arry 与 &arry 的加1能力不同
对于arry,这个地址为该数组的首元素的的地址
对于&arry,这个已经把整个数组看成一个整体,而向+1则加的是整个数组的字节大小

int arry [5] = {1,2,3,4,5};
arry + 1//指针往后移动4字节
&arry +1//指针往后移动20个字节
int arry[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4] = arry;
int (*p)[3][4] = &arry;

一道题

int ar[5][2] = {1,2,3,4,5,6,7,8,9,10};
int (*s)[2] = &ar[1];
int * p = ar[1];
printf("%d \n",s[2][3]);//*(*(s+2)+3)
printf("%d \n",p[3]);

柔性数组:
在c语言中,可以使用结构体产生柔性数组,数组的大小为0或者不定义,对于编译器来说,这个数组不占空间,它可以动态分配空间,但是它的数组的定义必须放在最后

用法:

struct array {
	int size;
	char data[];
};
int main() {
	struct array* a2 = (struct array*)malloc(10);

}

此时data的大小为6个字节(10个字节减去int字节的大小)

要注意的点

struct array {
	int size;
	char data[];
};
int main() {
	struct array a1 = { 1,2,3,4,5 };
	printf("%d", sizeof(a1));//4
	
}

柔性数组它并不占空间,sizeof是在编译时根据类型计算大小的,所以为4

九.函数

为什么c语言不可以重载,而c++可以重载?

采用名字粉碎技术
c和c++的函数在内部是通过修饰名来识别,修饰名是编译器在编译函数后形成的字符串。

c语言的名字修饰规则特别简单,是函数在编译之后,只是在原来的函数名称前面加了" _ ",
让我们看一个反汇编代码
在这里插入图片描述
这个是mian函数编译后的修饰名
当同名的函数出现时,因为编译后的函数名称都相同,所以函数不能重载

而对于c++来说,

c++的规则
1.以?标识函数名的开始,后跟函数名
2.函数名后面以"@@YA"标识参数表
3.参数表的符号表示
X: void
D : char
E:unsigned char
F:short
H:int
I:unsigned int
j:long
K: unsigned long
M:float
N:double
_N:bool
PA:表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个0代表一个重复
4.参数的第一项为该函数的返回类型,其后依次跟参数的数据类型,指针标识在其所指的数据类型前
5.参数表后以"@Z"标识整个名字的结束,如果没有参数,则以Z结束
int add(int a,int b){
return a+b;
}
它的修饰名为 (?add@@YAHHH@Z)

1.库函数 用户只需要在自己的程序中引入头文件,就可以使用该头文件中的函数
2.自定义函数 函数返回值类型 + 函数名+形参列表+函数体
函数的调用前面必须有函数的定义或者函数的声名,因为代码是从上往下依次执行的

main函数的三种形式

  1. int main(){
    return 0;
    }
    2. int main(int argc,char *argv[]){
    return 0;
    }
    3. int main(int argc,char * argv[],char *envp[]){
    return 0;
    }
    argc是argv中字符串的个数
    argv可以通过命令行来传递参数
    envp存放环境变量
    传进来的字符串存储在为主函数分配的栈帧的下面
    (详情请等待我之后的博客)

函数的声名

 int add(int a,int b);
 int add(int ,int);//可以只写类型
 
int add(int a,int b);
int mina()
{
  int x=2,y=1,z;
  z=add(x,y);//函数的调用
  z=add(int x,int y);//false 不可以这样写
  
  
}
int add(int a,int b);
int main()
{
 add;//函数的地址
}

4.如果函数有返回值的话函数的返回值,那么它的返回值是通过寄存器把这个值带回去的。

5.return 终止函数,如果是函数中,则返回调用者,如果在mian函数中,则返回给操作系统
如果返回值为void ,则return 可以不写,也可以这样写: return; 在有的编译器中也可以这样写 return void;
6.exit,直接终止程序

 1.exit(0);程序正常结束 和 exit(EXIT_SUCESS);等价
 2.exit(1);程序异常结束  和 exit(EXIT_FAILURE);等价

十.指针

1.指针的简介及用法
%p 控制打印地址值
32位操作系统指针为4字节
64位操作系统指针为8字节

类型对指针的作用
1:指针加1的能力
2:指针向下解析的大小

int mian()
{
   int a;
   printf("%p",&a);
   return 0;
}

*的用法

 int mian()
 {
    int a=1,b=1;
    b=a*5;//乘法
    int *p;//定义
    int *p=&a;
    *p=5;//解引用
 }

2.存放模式
小端模式:高位存放在高地址
大端模式:高位存放在低地址

c语言中按小端模式存放

请看下面的图片;7b相比于f8为高位,所以在第二张图片中,7b存放在高地址在这里插入图片描述 在这里插入图片描述

指针为什么要有类型?
指针存的是变量的首地址,而解析时,找到该地址,然后看该指针的类型,如果是Int类型,则向下解析4个字节
所以假如把int 类型的变量的地址赋值给float类型的指针时,当解析时,就会按照float类型的地址进行解析,所以会出现错误

int main()
{
  int a = 0;
  char* p1 = &a;
  short*p2 = &a;
  int* p3 = &a;
  printf("%p",p1); 
  printf("%p",p2); 
  printf("%p",p3); //这三个打印的都一样
}

3.指针类型
1.正常指针
2.野指针 :定义之后没有赋值
3.空指针 :定义后初始化为NULL
4.失效指针 ;调用变量时还处于它的生存期时,则没有失效

int * p()
  { 
    int a =100;
    int * p = &a;
    return p;
  }
int main()
{
  int *k = p();
  printf("%d",*k);//ERROR,失效指针,a定义在另外一个函数栈帧中,虽然函数结束后,a变量中存储的值没有被清零,但是这块空间的使用权已经交给操作系统了。
  int a = 10;
  int *p =NULL;
  if(a==10)
  {
   int x =20 ;
   p=&x;
  }
  printf("%d",*p);//x的范围之中if控制的代码块中,此时也是失效指针
}

一道题

void fun(int*p)
{
 static int b = 20;
 *p = 2;
  p = &b;
} 
int main()
{
  int a = 0;
  int* s = &a;
  fun(s);
  printf("%d %d",a,*s);// 2 2 此时p指向的是b的地址,不过p和s没有任何关系,所以*s 的值为2
}

要注意的点

int main()
{
  int ar[5] = {12,23,34,45,56};
  int * p = ar;
  int x=0,y=0;
  x = *p++;
  y = *p;
  printf("%d %d\n",x,y);//12,23
  x = ++*p;
  y = *p;
  printf("%d %d\n",x,y);//24 24  记住*p是p所指向的那个存储单元,而不是里面存储的值
  x = *++p;
  y = *p;
  printf("%d %d\n",x,y);//34 34
  
  
}

记住*p是p所指向的那个存储单元,而不是里面存储的值

int main()
{
 int arry[5] ={1,2,3,4,5};
 int *p1 = &a[0];
 int *p2 = &a[4];
 int *p3 = &a[5]; 
 printf("%d",(p2-p1));//4 并不是16
 printf("%d",(p3-p1));//5 c语言中没有下角标越界的检查
}

要注意的一道题

	const char* str[] = { "hello","new","printf","scanf","const","main","static" };
	const char* s = str[2];
	int n = sizeof(str) / sizeof(str[0]);
	for (int i = 0; i < n; i++)
	{
		printf("%s\n", s);
		s++;
	}
	printf("%d\n", sizeof(str));
	printf("%d\n", sizeof(str[2]));
	return 0;

	结果为printf,rintf,intf,ntf,tf; 28,4
	str 为一个存储五个指向数据区的字符串的指针,

3.1.1指针的类型

int * P[10];//一个长度为10的数组,每个元素是指向int*的指针
int (*p)[10];//指向长度为10的数组的指针
int * (*p)[10];//指向长度为10的数组的指针的指针
int * fun(int a,int b);//返回一个整型指针
int (*fun)(int ,int );//一个形参为(int,int),返回值为int的函数指针
int *(*fun)(int,int);//一个形参为(int,int),返回值为int*的函数指针
int (*fun[3])(int,int);//一个长度为3的数组,每个元素为指向形参为(int,int)返回值为int的指针
    double **s;
    double *p0,p1,p2,p3;
    double a0,a1,a2,a3;

在这里插入图片描述
1.s+1:p1地址
2.*s+1:a0地址
3.**s+1: a0+1

十一.结构体

程序员自己设计出来的类型

1.注意点

1.不能在定义的时候给它的成员变量赋值,因为它是定义,像一张图纸,并没有开辟空间,所以并不能赋值。
2.结构体变量在定义时,如果给所有成员变量都没有赋值,那么所有成员变量都是随机值,如果只给部分成员变量赋值,那么其他的成员变量的值默认为0
3.结构体变量之间可以互相赋值

struct student
{
  int age = 4;//ERROR
  char [20] name;
};
int main()
{
 struct student s ={4};//如果只给一部分成员变量赋值,那么其它的成员变量默认值为0
 struct student b;
 b=s;//结构体类型的变量之间可以相互赋值,因为它也是数据类型,只不过是自己设计的
}

2.访问成员变量

  1. 通过"."
  2. 通过"->"结构体指针使用的方法 例 sp->age 等同于(*sp).age
int main()
{
  struct student
   {
     int age;
     char [20] name;
   };//大小 4+20字节
   struct student s1 = {4,"小明" };//定义并初始化
   struct student sp = &s1;
   printf("%d",s1.age);
   printf("%s",s1.name);
   printf("%d",sp->age);//等于(*sp).age
   printf("%s",sp->name);//等于(*sp).name
}

也可以这样

 int main()
 {
   struct sutdent
   {
     char [] name;
     int age;
   };
    struct student student;//定义了一个名字为student 的struct student类型的变量。(为什么可以起student这个名字,因为它的类型名为struct student)
 }

尽量不要用结构体变量做函数的形参,因为结构体变量也是数据类型,它也会在函数的栈帧中开辟空间,并把实参的值传过去,这样太损耗时间和空间。传递的话尽量使用指针。

如何实现数组平移 ?
平移3 int a ={1,2,3,4,5,6,7,8,9,10};
交换前3位,交换后3位 此时 a 为{3,2,1,10,9,8,7,6,5,4};
再整体一交换 此时 a 为{4,5,6,7,8,9,10,1,2,3}

利用结构体

struct Ar_move {
		int a[10];
		int index;
		int maxsize;
	};
	int Get_Elem(const struct Ar_move *par,int pos)
	{
		if (par == NULL || pos <0 || pos>par->maxsize )
			exit(1);
		return par->a[(par->index + pos) % par->maxsize];

	}
	void Left_move(struct Ar_move* par, int k)
	{
		if (par == NULL)
			exit(1);
		k = k % par->maxsize;
		par->index = par->index + k;
	}
	int main()
	{
		struct Ar_move a = { {1,2,3,4,5,6,7,8,9,10},0,10 };
		Left_move(&a, 3);
		for (int i = 0; i < 10; i++)
		{
			printf("%d", Get_Elem(&a, i));
		}

	
	}

3.结构体大小,关于字节对齐
3.1.使用预处理指令改变对齐方式

#pargma pack(n) n为1,2,4,8,16

3.2.对齐规则(没有使用预处理指令改变对齐方式)

1.结构体变量的首地址,必须是结构体变量中的“最大基本数据类型成员 所 占字节数”的整数倍。
2.结构体变量中每个成员相对于结构体首地址的偏移量,都是该成员基本数据类型所占字节的整数倍。
3.结构体变量总的大小,为结构体变量中最大基本数据类型的字节的整数倍、

struct a
{
  char cha;
  int b;
  char chc;
}


在这里插入图片描述
cha占一个字节,因为下面b为4个字节,此时偏移量为1,所以在下面填充3个字节,此时chc偏移量为9个字节,因为9不能被4整除,所以再在下面填充3个字节,所以此结构体大小为12个2字节

struct sdate
{
 int year;
 int month;
 int day;
};
struct student
{
 char s_id[10];
 char s_name[8];
 struct sdate birthday;
 double grade;
};

这种需要把结构体拆开,对齐的时候按Int year,int month,int day 来看,而不是按整个结合体来对齐
3.3计算偏移量
1.

struct Node
	{
		char chb[3];
		int d;
		double e;
		char chf[3];
		int g;
	};
	int main()
	{
		struct Node x;
		int len = (char*)&x.d - (char *)&x;
		printf("%d", len);

	
	}
struct Node
	{
		char chb[3];
		int d;
		double e;
		char chf[3];
		int g;
	};
	int main()
	{
		int len = (int)(&((struct Node*)0)->d);
		printf("%d", len);

	;
	}

为什么要字节对齐?
1.内存的大小是字节,但是,计算机CPU读取内存时并非逐字节读取,而是以2,4,8,的字节块来读取因此对数据类型的地址做出限制
2.有些平台每次读都是从偶地址开始,如果从偶数开始读,则只需要一个周期,如果存在奇地址,则需要两个周期,再对数据进行拼凑
3.不同平台的对齐方式不同,同样的结构在不同的平台大小不同,如果在传输数据的时候,可能会出现混乱

十二.共用体

共用体(联合体)对一块地址以不同的方式解析
1.共用体的每一个数据成员的起始地址都相同,所有成员共用同一段内存,修该一个数据成员会影响其他成员的值
2.共用体所占内存大小,等于最大成员所占大小,共用体采用内存覆盖技术,同一时刻只能保存一个成员的值,对新的成员赋值,则会更改原来成员的值

在这里插入图片描述

	union a
	{
		int a;
		char s[4];

	};
	union b
	{
		int a;
		char s1, s2, s3, s4;
	};
	union c
	{
		int a;
		struct {
			char s1, s2, s3, s4;
		};
	};

利用共用体证明数据的存放是小端地址

union a
{
  short b;
  char c[2];
};
int  main()
{
 union a a1;
 a1.short = 0x0001;
 if(a1.c[0] == 1)
 { 
   printf("为小端存放模式");
 }else
 {
  printf("为大端存放模式");
 }
}

利用共用体转化IP地址

union ip
	{
	  unsigned int addr;
	  struct
	  {
	   unsigned char s3,s2,s1,s0;
	  };
	};
		void print_IP(int addr,char * buff)
	{
		union ip a= { addr };
		printf("%d.%d.%d.%d\n", a.s0, a.s1, a.s2, a.s3);
		sprintf(buff, "%d.%d.%d.%d", a.s0, a.s1, a.s2, a.s3);
	}
	int main()
	{  
		char buff[20] = { 0 };
		print_IP(2148205343,buff);
		printf("%s", buff);
	;
	}

printf底层调用了sprintf,printf是把数据格式化之后打印到屏幕上,而ssprintf是把数据格式化之后送到指定的区域
如:printf(“%d %d\n”,1,2);
转化之后为
1 2\n\0,然后把它显示在屏幕上
在这里插入图片描述
在这里插入图片描述
共用体有名字的时候是一个声名,不占内存

十三.动态内存

1.分配方式

分配内存的时候有两种方式

占用块:一般在低地址区,给用户分配的部分
空闲块:一般在高地址区,未给用户分配的部分

1.1.用户请求分配时,直接在高地址去分配,不理会分配给用户的空间是否空闲,直至空闲块无法分配,系统才去回收那些不再使用的空闲块(free之后的占用块),并重新组织内存,将所有空闲区域连接在一个成为一个大的空闲块
1.2.用户请求分配,系统先去找有没有合适的用户不使用的空闲块(free后的占用块),如果没有再去空闲块分配
vs为第二种

栈的默认大小为1M,如果你使用的是VS的话,可以在这里修改栈的大小在这里插入图片描述
尽管在C99中规定了数组可以动态开辟,就像这样
scanf(“%d”,&n);
int arr[n];
但是大多数编译器还没有实现,因为栈的空间固定是1M,如果像这样让用户直接输入,如果输入的值大于1M,这样会之间冲毁栈帧。

2.如何动态分配内存
常用函数 malloc calloc,realloc,free,需要引用stdlib.h或者malloc.h

2.1.void * malloc(size_t size ) size为字节数

在这里插入图片描述
在这里插入图片描述
fd为上越界标志,它为所有开辟的空间默认初始为cd

如果malloc的字节为0?它还会不会分配空间?

int main() {

	int* t = (int*)malloc(0);

}

会分配,这样的话返回的地址就为两个越界标志的地址
在这里插入图片描述
因为这样也指的是一块地址,所以它也可以对这地址进行操作,不过这样特别危险

2.2.void * calloc(size_t num,size_t,size)

分配好之后,每个空间初始为0
底层调用了malloc,相当于这样的

 void* calloc(size_t num, size_t size) {
	void* t = (num * size);
	if (t == NULL)
		exit(1);
	if (t != NULL) {
		memset(t, 0, num * size);
	}
	return t;
}

**3.3 void recalloc(void ptr,size_t new_size)

重新分配内存块,当传入的地址为NULL时,和mallc基本一样

int main()
{
    int* p = (int*)malloc(sizeof(10));
    if (p == NULL)
        exit(1);
    int* newp = (int*)malloc(sizeof(15));
    if (newp == NULL)
        exit(1);
    p = newp;

}

尽量新定义一个指针去开辟,因为如果分配失败recalloc返回值为NULL,这样会把原来的地址弄不见

int main(){
 int* p = (int*)malloc(sizeof(10));
    if (p == NULL)
        exit(1);
     p=(int *)malloc(5);
}

最好不要使用这个减容,因为这样会造成内存碎片

小心野指针的使用,释放之后一定要把指针置为空


int main() {


	int* t = (int*)malloc(10);
	free(t);
	int* s = (int*)malloc(10);
	*t = 100;
	printf("%d", *t);
	free(t);
	return 0;
}

t释放之后并没有置空,所以它还指向这块空间,而vs采取的是上面我说的第二种方式,所以s也指向的是t之前的那块空间,这样的话,t可以修改s指向的那块空间,也可以释放,这样是非常危险的,所以一定要养成好习惯,指针一释放立马置为NULL

malloc ,calloc,realloc这三类分配的空间的结构
头部信息 28字节
上越界
空间
下越界
头部信息可以分为三部分:
1.第一部分,标志信息,用于标记这块空间是否释放
2.第二部分,系统把所有分配的空间都相当于用一个链表连接起来(便于在程序结束后释放空间),这部分存储上一块分配空间的地址还有下一块分配空间的地址
3.第三部分,存储了分配这块空间的大小。
分配的内存可以释放一半吗?

int main(){
int * p=(int *)malloc(sizeof(10));
p+=5;
free(p);
return 0;
}

所以这样的操作是不行的,空间释放的时候,先向上偏移到头部信息,再根据头部信息来释放空间,而且空间的分配都是整块整块分配的,绝对不会存在这种释放一半的情况。

十四.文件

1.四种流
stdin 标准输入文件 一般指键盘 scanf getchar等默认从stdin获取输入
stdout 标准输出文件 一般指显示屏 printf putchar 等默认向stdout 输出
stderr 标准错误文件 一般指显示器 perror等默认向stderr输出
stdprn 标准打印文件 一般指打印机

默认下 stdin是行缓冲,它默认存放在一个buffer中,当输入数据时,数据会一个个存放在缓冲区,只有当输入\n时,才会一个一个存缓冲区取数据
默认下 stdout是行缓冲,它默认存放在一个buffer中,只有当换行时,才会输出到屏幕
stderr默认无缓冲会直接输出

1. 打开文件函数 原型FILE * fopen(const char * filename,const char * mode)
filename,文件名,包含路径
mode,打开模式
“r”,只读 文件必须存在,否则打开失败
“w”,只写 若文件存在,则清除文件重新写入,否则,则新创建文件
“a”,末尾只写 若文件存在,则将位置指针移到文件末尾,在尾部追加数据,否则,则新创建文件
“r+”,读写 文件必须存在
“w+”,读写 新创建文件
“a+”,读写 在"a"的基础上,新填读功能
“rb”,二进制只读 以二进制形式读
"wb"二进制只写 以二进制形式写
"ab"二进制末尾只写
"rb+“二进制文件读写 功能模式像"r+”,只不过以二进制形式
"wb+“二进制文件读写 功能模式像"w+”,只不过以二进制形式
"ab+“二进制文件读写 功能模式像"a+”,只不过以二进制形式
如果打开失败,则会返回一个NULL

当对文件读取时分配的缓冲区
在这里插入图片描述
实际对磁盘的读写是通过文件流对磁盘进行读写,当我们执行对文件写的操作时,实际把数据写入到fp对应的缓冲区大小为4k, 因为每一页或者每个数据块的大小都是4K),当执行fopen之后,它会把数据回写到磁盘
流按方向分:
输入流: 从文件获取数据称为输入流,
输出流 : 向文件输出数据称为输出流
按数据形式分:
文本流: 文本流是ASCALL序列,比如把123写入文件中,则写入的是对应的字符123,_itoa和这个很像
二进制流 : 二进制流是字节序列
简单用法

int main() {
	int arr[5];
	int n = sizeof(arr) / sizeof(arr[0]);
	FILE* file = fopen("text.txt", "w");
	if (file == NULL) {
		exit(1);
	}
	for(int i =0;i<n;i++)
	fscanf(stdin, "%d", &arr[i]);
	for (int i = 0; i < n; i++) {
		fprintf(file, "%d ", arr[i]);
		fprintf(stdout, "%d ", arr[i]);
	}
	fclose(file);
	file = NULL;
	int arr2[5];
	FILE* rfile = fopen("text.txt","r");
	if (rfile == NULL) {
		exit(1);
	}
	for (int i = 0; i < n; i++) {
		fscanf(rfile, "%d", &arr2[i]);
		printf("%d", arr2[i]);
	}
	
}

二进制流基本用法


int main() {
	int arr[] = { 1,2,3,4,5 };
	int n = sizeof(arr) / sizeof(arr[0]);
	FILE* file = fopen("t.txt", "wb");
	if (file == NULL) {
		exit(1);
	}
	fwrite(arr, sizeof(arr[0]),n, file);
	fclose(file);
	file = NULL;
	int arr1[5];
	FILE* rfile = fopen("t.txt", "rb");
	if (rfile == NULL) {
		exit(1);

	}
	fread(arr1, 4, 5, rfile);
	fclose(rfile);
	rfile = NULL;
	printf("%d", arr1[1]);
	
}

gets_s,读取的时候还要有一个空间来读取\0

2.文件位置函数
2.1 :feof 检测是否到达文件末尾,如果到到达文件末尾,则为非0值,否则则为0值
2.2:rewind 将文件内部指针指向文件的开头(通常用这个清除stdin的缓冲区)
2.3: ftell 返回当前文件位置指示值
2.4:fseek 将文件位置指示符移动到文件中指示的位置
2.5: long ftell(FILE *stream)
2.6: int fseek(FILE *stream ,long offset,int origin)
第一个参数stream为文件指针
第二个参数offset为偏移量,正数表示向右偏移,负数表示向左偏移
第三个参数origin设定从文件的哪里开始偏移,可取值为:
SEEK_CUR(当前位置),SEEK_END(文件末尾),SEEK_SET(文件起始)

简单用法

int main() {


	int value = 0, pos = 0;

	FILE* file = fopen("info", "rb");
	while (scanf("%d", &pos), pos != -1) {
		fseek(file, pos * 4, SEEK_SET);
		printf("%d\n", ftell(file));
		fread(&value, sizeof(int), 1, file);
		printf("%d\n", value);
		printf("---");
	}
	fclose(file);
	file = NULL;
}

读文件的一个问题 注意!!!
当文件以文本形式打开时,原来文件中的 0D(\r) 0A(\n),只会把0A读入到缓冲区****而使用ftell计算大小时,却会把0D计算上,所以尽量使用二进制读取文件,
为什么会出现这个原因哪,在计算机发明前,对于打字机来说:\r: 把位置移到这行开头\n:把位置移到下行这个位置,而计算机发明之后,就用\n代替了这两个功能
读的时候可以这样读

int main() {
	FILE* file = NULL;
	errno_t x = fopen_s(&file, "源.c", "rb");
	int number=0;
	
	if (file == NULL) {
		exit(1);
	}
	fseek(file,0,SEEK_END);
	number = ftell(file);
	char* buff = (char*)malloc(sizeof(char) * number);
	rewind(file);
	fread(buff,sizeof(char),number, file);
	printf("%s", buff);
	fclose(file);
	file = NULL;
	
}

带缓冲区的原因
当cpu遇到stdin的函数时,先去干其他的事情,当输入\n时,在过来把缓冲区的数据取出来。为了使两个运行速度差别很大的设备可以匹配起来,加快程序的运行(键盘输入的速度与cpu执行速度相差太大)

十五.void 类型

如果给参数列表为void的函数传入参数会怎么样?

	
	void fun1(int a)
	{
	  int* p = &a;
	  printf("%d \n",*p);//12
	  p++;
	  printf("%d \n",*p);//23
	  p++;
	  printf("%d \n",*p);//45
	}
	int main()
	{
	 
	  fun1(12,23,45);
	  return 0;
	}

上面fun1打印的结果依次为12 13 45
形参为void只是给程序员看的,尽管函数形参设置为void但是它还是会接受值(实参入栈是由右向左)如果传入过多的话,会把函数的栈帧冲掉

1无类型指针可以指向任意地址,

void 不能定义变量,但可以定义指针,它可以指向任意变量的地址,包括自己的地址,所有它也可以被称为泛型指针
泛型指针的使用

int main()
{
  int a;
  double b;
  int arry[5];
  void* p = &a;
  p = &b;
  p = arry;
  p = &arry; 
  p = &p;
  char* c = (char *)p;//把泛型指针给其它指针赋值时,必须强制类型转化
}

2.对于标准c 和 GUN,无类型指针都不能解析,
3.无类型指针可以sizeof计算这个指针的大小,,而不能计算他所指向的大小

int main()
{ 
  int a;
  void* p =&a;
  int size1 = sizeof(p);//ture
  int size2 = sizeof*p);//ERROR
}

4.对于标准c:无类型指针无法进行加减操作,如 p++,p–;
对于GUN,它认为void* 和 char *一样,所有可以p++;

初始化函数

void my_memset(void* dest,unsigned char val,size_t count)
{
  assert(dest!=NULL);
  char* cp =(char*)dest;
  while(count--)
  {
    *cp = val;
    cp++;
  }
}
int main()
{
  int ar[5];
  my_memset(ar,0,sizeof(5));
  for(int i=0;i<5;i++)
  {
   printf("%x",ar[i]);//0x0a0a0a0a
  }
}

拷贝函数


void* my_memcpy(void* dest,const void* src,size_t,count )
{
  if(dest == NULL || src ==NULL)
  return dest;
  char* dp = dest;
  const char* sp =src;
  while(count--)
  {
    *dp = *sp;
    dp++;
    sp++;
  }
  return dest;
}

十六.预处理

c语言提供多种预处理功能,如宏定义(#define),文件包括(#include),条件编译(#ifdef),合理使用预处理编写的程序便于 阅读,修改,移植,调试

1.预处理要注意的点

1.1.所有预处理指令必须以#开头,而且预处理指令必须单独占一行
1.#define macro__name char_sequence
#define定义了一个宏名标识符和一个字符序列,在源程序中每次遇到这个宏名(一般使用大写),就用这个字符序列进行替换,过程称为宏替换
gcc编译器可以通过命令行给宏定义符号
在标识符和字符序列之间可以有任意个空格
1.2.宏名可以嵌套定义

#define ONE 1
#deinfe TWO ONE+ONE

1.3.注意宏替换会把后面一行所有的字符序列全部替换(在不使用续航符的情况下)
在这里插入图片描述
下面是这个.c文件生成的.i文件
在这里插入图片描述
使用续航符,续航符后不允许跟任何符号,空格也不可以
在这里插入图片描述
在这里插入图片描述
这让我想起来了字符串的一种这样写的方式
在这里插入图片描述
当然也可以这样
在这里插入图片描述
这样也可以输出
在这里插入图片描述
.2.预处理的变元
这样和函数很像的用法我们称为宏函数,它提高了代码的执行速度减少了调用函数的开销不过会造成代码的膨胀
每个变元都要加括号防止展开时于其他运算符进行结合
在这里插入图片描述
也可以有多个变元
在这里插入图片描述

3.预处理器运算符
1.# 通常称为字符串化运算符,使得它后面的变元变成带双引号的串
2.## 通常称为链接运算符,用于链接两个符号
3.#@ 将后面的变元字符化
在这里插入图片描述

如果同时把多个数字字符串化
在这里插入图片描述
1 2 3 4
31 32 33 34( 对应的ASCALL值的16进制)
然后把最低位赋值给a

在这里插入图片描述
4.宏展开的过程
1.先执行字符串化操作
2.再执行链接操作
3.再对变元进行替换
4.对展开的结果查看是否有#define定义的符号,如果有,则重复上一过程,但不允许对宏名二次展开

#define INC(x) x+1
#define STR(b) #b
int main() {
STR(INC(2));
}
#define INC(x) x+1
#define STR(b) b
int main() {
STR(INC(2));
}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第一个把STR展开后变成了:“INC(2)”,虽然它于宏名相同,但是因为它已经是字符串了所以不再展开
在这里插入图片描述
在这里插入图片描述
4.2数据类型也可以是变元
在这里插入图片描述
5.泛型编程
在这里插入图片描述
6.自动生成代码
在这里插入图片描述
和这样很像
在这里插入图片描述
7.预定义宏 已经定义好的宏
1_FILE_ 展开问当前文件名,为字符常量
2._LINE_展开为源文件行数,为整形常量
3._DATE_展开为日期,格式为"mm dd yyyy"的字符常量
4._TIME_展开为编译时间,格式为"hh:mm:ss"的字符串常量
5._STDC_判断当前编译器是否为标准c编译器,如果是,则展开成1
6._cplusplus 如果以c++方式编译,则该宏存在
7. func 打印当前函数的名称 它不是宏,而是一个静态数组,在编译的时候,编译器会把当前函数的名字放在这个数组中
8.条件编译指令

    #if 
    #else
    #elif
    #endif

在这里插入图片描述
条件后面只能为 宏定义的变量,而且只能为整形.

条件编译是在预处理的时候就处理的,因为如果是局部变量或者全局变量,静态变量,在预处理的时候都默认为0,所以只能为宏定义的变量

    #ifdef
    #ifndef 
    这两个也算条件编译,只不过只是检测是否宏定义

在这里插入图片描述

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值