《C和指针》—读书笔记 潦草版

一、基础知识

  1.main函数

#include<stdio.h>
int main()
{
	printf(“Hello World!\n”);
	return 0;
}

        #include将头文件添加展开,main函数为C语言程序的入口点,printf函数为格式化输出函数。

  2.注释编译

        /*注释*/

        程序执行过程:预处理、编译、链接、执行

        源文件.c  头文件.h  目标文件.obj  执行文件.exe

        在宿主环境中,程序将和启动程序链接,程序运行时会分配一个堆栈,用来存储函数的局部变量和返回地址,程序也可以使用静态内存。

  3.标识符

        C语言关键字不能为标识符,C语言区分大小写,标识符以字母或下划线开头,可以是字母、数字、下划线。

  4.字符

ASCII
转义字符:\?  \’  \”  \\  \a  \n  \b  \r  \t  \ddd  \xdd

      5.输入输出函数

    printf(); //从后向前执行,格式化输出函数
    scanf();  //格式化输入函数
    //例如%d %f,输出格式决定内存中的数据如何显示
    char c;       scanf("%d",&c);  //会写入int型地址大小
    float f=3.14; printf("%d\n",f);//会将float的IEEE754浮点格式保存的内存值直接解释为int打印
    //注意输入输出格式,和一些宽度符号等问题

      6.运行时环境

            函数调用时会将局部变量保存在堆栈帧中,会产生一个帧指针,变量的值通过该帧加上偏移量进行访问,在函数调用过程中不断产生局部变量和要保存寄存器值,因此不断变换帧指针。

    二、数据类型

      1.整形

            整形包括:字符型、短整型、整型、长整形,它们都分为有符号、无符号。

    char 、short、 int、 long、  long、 signed、 unsigned

            整形字面值:二进制0b开头、十进制、八进制0开头、十六进制0x开头

    字符常量:’A’、‘\n’、 ‘??(’、‘\377’、 ‘\xab’
    int a = 0b001111,b = 12,c = 012,d = 0x0f;
    char c1 ='A',c2 = '\n',c3 = '\666',c4 = '\x9f',c5 = ‘abc’;//c5=’c’
    

       2.枚举

      enum day{one=6,two,three=112,four,five};//默认1,2,3递增,可改变值 
      enum day a = 0x1234;					//枚举变量是整形变量
      int b = four;							//枚举的符号当作整形常量
      

        3.浮点型

              浮点型包括:单精度float、双精度double、扩展精度long double

      float f1 = 3.1415926,f2 = -25.,f3 = .5678;
      double d1 = 1.234e3, d2 = 123e-3;
      

        4.指针

      char *p = "abcd";//字符串的值是一个指针常量
      printf("%d,%d,%s\n",p,"abcd",p+1);
      运行结果:4210688,4210688,bcd
      

        5.声明与初始化

      char s;
      int a,b,c=1,d;//多个声明与初始化 
      int n[10];//数组下标0开始,编译器不检查下标的合法性,可以使用a[10] 
      char *p = “Hello”;//*号跟随变量 
      int* p1,p2,p3;//只有p1为整形指针变量
      

        6.typedef

      typedef 放在声明前面,可以将标识符等同于该类型
      typedef int *p;
      p p1,p2;//p1 p2 都是int* 
      #define q int*
      q q1,q2;//q2不是int*
      

        7.常量与const

      int const a=10;
      const int b=20;
      int *p1;			//普通指针 
      int const *p2;		//指向整形常量的指针 
      const int *p3;		//同上 
      int *const p4;		//指向整形的常量指针 
      int const *const p5;//指针和指向的值都不能修改
      

        8.作用域

               代码块作用域:{ }中的所有语句,内层标识符优先,从声明处到代码块结束有效

               文件作用域:在代码块之外声明的标识符,从声明处到文件尾有效 

        9.链接属性

              外部external:同一标识符的多次声明,所有源文件中只能有一个实体,如:全局变量、函数

               内部internal:同一源文件内所有声明是同一实体,不同源文件的声明分属不同实体,如:static修饰的全局变量、函数(static可以将external属性修改为internal)

              无none:多个声明被当作不同的实体 ,如:函数内的局部变量,函数的形参 

        10.存储类型

              变量存储的地方:普通内存、运行时堆栈、寄存器

              在代码块之外声明的变量存储于:普通内存(静态变量)

              在代码块之内声明的变量存储于:堆栈(自动变量)(static改变为静态变量)

               被声明为 register的变量存储于:寄存器

               静态变量默认初始值0,自动变量默认初始值为该内存值

        11.static关键字

              static用于全局变量和函数,从external改为internal

              static用于局部变量,从自动变量改为静态变量  

      三、语句表达式

        1.空语句

              只包含一个分号;

        2.表达式语句

      x = y + 3;  ch = getchar();  y + 3;  printf("Hello");

        3.代码块

              一对花括号{ }

        4.if语句

              c语言无布尔类型,0为假,其他都为真

              else从属于最近上方的if

               if和else后只能跟一条语句,或用花括号

        5.while语句

              do-while语句至少执行一次,表达式为假则结束while循环

        6.for语句

      for(表达式1;表达式2;表达式3)

              表达式可以是任何类型,比如逗号表达式

              表达式都可以省略,但分号不能省略,其中表达式2省 略时其值为真

              for、whiledo-while语句可以互相组合嵌套 

              使用continue后,会先执行表达式3

        7.switch语句

              switch中的表达式值会直接定位到相同的case处,执行后面的所有语句,遇到break才跳出switch语句,当无法定位则定位到default处,执行后面的所有语句,遇到break才跳出switch语句,casedefault的先后顺序可以任意

              switch表达式是intchar、枚举类型

              case中必须是常量表达式,且不可重复

              多个case子句可共用同一语句,case后面语句组可不用{ }switch语句可嵌套

        8.goto语句

              goto 标签

        9.break与continue

              break结束一层循环体,continue结束一次循环  

      四、操作符

        1.运算符

      算数运算符:+ - * / %    
      除了%其它都适用于整型和浮点型
      		移位运算符:<< >>
      			左移位:丢弃左边,右边补0
      			右移位:逻辑移位左边补0,算数移位左边补符号位
      位操作符:&  |  ^ 
      赋值运算符:= 和 ( + - * / % << >> & ^ | )复合赋值
      单目操作符:!  ++  --  &  sizeof  ~  +  -  (类型)
      			++a前缀操作先增后复制值,a++后缀操作先复制值后增
      			++a的结果是a值的副本,并不是变量本身,++a=10语句错误!
      关系运算符:> >= < <= == !=
      			满足关系值为1,否则为0
      逻辑操作符:&& || 
      			短路操作:&&左边假不检查右边得假,||左边真不检查右边得真
      			满足关系值为1,否则为0
      条件操作符:? :
      逗号操作符:,
      			从左向右执行,表达式的值为最后的表达式的值
      下标引用、结构成员:[ ]、.、->
      			array[ ] 等价 *(array+下标)
      

        2.布尔值

              0是假,非0为真(C语言没有布尔类型)

        3.左值与右值

      a = b + 1;

              a出现在赋值运算左边称为左值,b+1称为右值(字面值常量不是左值)

        4.表达式求值

              整形运算提升:char、short先转为int进行运算,结果截断

      long double >double >float >unsigned long int >long int > unsigned int >int

              不同类型运算前,根据优先级转换后进行运算

        5.操作符属性

              求值顺序由3个因素决定:操作符优先级、操作符结合性、操作符是否控制执行顺序

              优先级:两个相邻的操作符哪个先执行取决于优先级

      a*b + c*d +e*f(a*b + c*d先执行)

              结合性:从左向右执行或从右向左

              执行顺序:控制执行顺序(如, &&、||? :),其中逗号从左向右控制,&&||从左向右控制并且可以短路控制,? :根据判断控制

              在一串操作符中,首先根据优先级执行,如果有控制顺序操作符则控制顺序,否则当优先级相同时根据结合性执行。 

      五、指针

        1.内存与地址

      float f = 3.14;
      int *p = &f;// *p的值为1078523331
      

              内存中每个单元都有对应的地址,其中存放的数据可以进行不同类型的解释

      int a=10,*p=&a;
      *(char*)p、*(short*)p、*(int*)p、*(float*)p、*(double*)p)

        2.指针变量

      int a=10,*p=&a, **q = &p;

              指针变量的地址:&p等价q

              指针变量的值:p等价&a等价*q

              指针变量指向的值:*p等价a等价**q*解引用或间接访问)

        3.未初始化与非法指针

      int *p; //静态指针变量初始化0,否则不初始化
      *p = 10;

              p的值未初始化,产生非法地址错误,或该地址未内存对齐错误

              当指针无指向时,一般用NULL赋值

        4.指针常量

      *&a=10; //&a是一个指针常量
      *(int*)10 = 10; //强制转换为指针常量

        5.指针表达式

      int a=10,*p=&a;
      *p+1==a+1
      *(p+1)==*(&a+1)
      ++p//先p+1再复制p的值 
      p++//先复制p的值再p+1 
      *++p//先p+1再*(p+1) 
      *p++//先复制p的值,再p+1,再*p
      ++*++p//先p+1,再*(p+1),再*(p+1)+1 
      ++*p++//先复制p,再p+1,再*p,再*p+1 
      

              程序一:计算字符串长

      char *str = "asc4353as3c25sacscaa";
      int len = 0;
      while(*str++!='\0')len++;
      

              程序二:在字符串数组中查找字符Q

      char *string[ ] = {"ascas","saasdsd","sads-Q-aasd",’zxcvzxcsac’,0};
      	char **str = string;
      	while(*str!=0)
      	{
      		while(**str!='\0')
      			if(*(*str)++ == ‘Q’)return 1;
      		str++;
      	}
      

         6.指针运算

      指针加法:char:1、short:2、int:4、double:8

              指针减法:两个指针指向同一数组时,减法可得元素数量

              关系运算:> >= < <=,可以比较指针得出位置关系

        7.指针高级声明

      int *f();		//返回值int*类型 
      int (*f)();		//f是函数指针
      int *(*f)();	//f是函数指针,返回值int*类型	
      int *f[];		//f是数组,元素为int*类型 
      int (*f[])();	//f是数组,元素为函数指针
      int *(*f[])();	//f是数组,元素为函数指针,返回值int*类型
      int (*f())();   //f是函数,返回值为函数指针,该函数指针返回值int
      int (*f(int,int(*)(int)))();   //对f函数的参数增加,参数1:int,参数2:int(*)(int)
                                     //int(*)(int)是一个函数指针,作为f的回调函数

      六、数组

        1.一维数组

      int num[ ] = {1,2,3,4,5};  //数组初始化,将自动计算长度
      int num[5]={1,2,3,4,5,6}; //错误,不能超过数组长度
      int num[5]={1,2,3,4};   //不足时,补0
      char str[ ] = “hello”;   //初始化字符数组
      char *str = “hello”;    //字符指针指向字符串
      

              数组名:指针常量,表示第一个元素地址,sizeof和&时代表整个数组

              下标访问a[1]与间接访问*(a+1)完全相同,但不能执行a++,因为a是常量

        2.指针与一维数组

      int a[5],*p=a;
      指针访问数组:p[1]或*(p+1)

        3.一维数组与函数传递

      //字符串复制函数:
      void strcpy(char buffer[],char const *string){
          while((*buffer++ = *string++)!='\0');
      }

              形参char buffer[ ] 等价char *buffer, 函数无法获得数组长度,除非传递长度参数

        4.多维数组

      int a[3][3]={{1,2,3},4,5,6,7};//依次赋值,不足补0 
      int b[][3][2] ={1,2,3,4,5,6,7};//自动计算数组大小,只有第一维可省略,否则无法推断大小
      

              二维数组a[3][3]的一些概念:

      a、a+1、a+2的类型为指向包含10个元素的数组的指针
      a[0]、a[1]、a[2]表示包含10个元素的数组等价于*a、*(a+1)、*(a+2)
      a[0][0]、a[1][1]、a[2][2]表示元素等价于**a、*(*(a+1)+1)、*(*(a+2)+2)
      

        5.指针与多维数组

      int a[3][3]={{1,2,3,},4,5,6,7};
      int *p,(*q)[3];  //q类型:3个整形元素的数组的指针 
      p = a[0];  //a[0]、a[1]、a[2]都指向一维数组,指针类型是:整形
      q = a;  //a、a+1、a+2都指向包含3个整形元素的数组的指针, 指针类型是:包含3个元素的整型数组。
      

              指针需要知道自己指向的类型,否则无法在解引用时获取存储空间大小。  

        6.多维数组与函数传递

      func(int *a); func(int a[]);
      int a[3];func(a);
      func(int (*a)[3]); func(int a[][3]);
      int a[3][3];func(a);
      func(int **a); //指向整型指针的指针,与指向整型数组的指针不同
      

        7.指针数组与命令行参数

      int *p[10]; //[ ]优先级高于* ,因此p[10]的10个元素都是整形指针
      char *str[]={"Hello World","Hello xiaoming"};
      //命令行参数:main(int argc, char *argv[ ]),argc表示参数数目,argv指向一组参数
      //第一个参数是程序的名字,argv指向一个指针数组,数组每个元素指向一个字符串
      

        8.字符串常量

              编译器把字符串常量存储在一块内存,并存储一个指向第一个字符的指针,是一个常量指针,可以对其下标引用、间接访问、指针运算

      "abc"、"abc"+1、*"abc"、"abc"[2]、*("abc"+2)

      七、函数

        1.函数声明和参数

      int *func(int a,int b[],char *);//声明时形参名称可以没有

              返回值类型:int *

              函数名称:func

              参数的个数和类型:整型、整形指针、字符指针

              C语言函数的形参均以“传值调用”传递,形参拷贝实参的值,值包括地址

        2.ADT和黑盒

               抽象数据类型ADT,这个技巧被称为黑盒,提供功能和接口,内部实现被屏蔽

              在一个文件中使用static将函数和全局变量由external转为internal,则无法在文件外部访问这些函数和全局变量 

        3.递归

              递归函数直接或间接调用自身,形参存储在运行时堆栈上,根据调用的层次不断产生形参进栈和寄存器值的保存,当每一层调用返回时变量从堆栈中销毁

              递归两个特点:终止条件、子问题

        4.可变参数列表

               函数原型中的参数数量和类型可变

              类型va_list、宏va_start va_arg va_end 定义于stdarg.h头文件

      void func(int num, …)
      va_list a; //定义一个va_list类型的变量a
      va_start(a,num)//将a设置为指向可变参部分的第一个参数
      va_arg(a,int)//取一个参数,a指向该参数,类型为int
      va_end(a)//结束处理a
      

              两个问题:宏无法判断参数数量和每个参数的类型,没有第一个参数将无法使用宏

              解决办法:通过参数传递参数数量,格式字符串传递每个参数的类型 

        5.函数指针

      void func(int);
      void (*p)(int) = &func;  //&可省
      func(10);
      (*p)(10);
      p(10);  //这三种调用方式等价
      

              回调函数:用户把函数指针作为参数传递给函数后,该函数将回调用户传入的函数

              函数指针数组:

      double add(double, double);
      double sub(double, double);
      double mul(double, double);
      double div(double, double);
      double(*opt[])(double, double) = { add,sub,mul,div };//定义函数指针数组
      int result = opt[0](1, 2);//使用
      

      八、结构体

        1.结构体声明与定义

      struct student{
      	int a;
      	char b;
      };  //声明一个student类型
      struct student{
      	int a;
      	char b;
      }s[10],*p;                    //声明student类型,并创建s[10]和*p,都为student类型
      struct student s[10],*p;      //创建s[10]和*p,都为student类型
      typedef struct{
      	int a;
      	char b;
      } student;
      Student s[10], *p;			  //创建s[10]和*p,都为student类型
      

        2.结构体成员访问

              结构体成员可以是:标量、数组、指针、结构

               普通变量:成员通过(.)点运算符访问

              指针变量:成员通过(->)运算符访问

               结构体自引用:成员是自身结构时,无法确定大小,但可以是指向自身结构的指针

      typedef struct{
      	int a;
      	stu *b;//错误,此时类型stu还未定义 
      }stu;      //类型只能使用stu 
      typedef struct student{
      	int a;
      	struct student *p;
      }stu;      //类型可使用stu和struct student
      

              结构体和数组一样,可以用{ }来初始化,或者用循环分别对成员初始化 

        3.结构体与指针

      typedef struct student{
      	int a;
      char c;
      }stu;
      stu s,*p=&s;
      

              使用p->a访问结构体成员,p的值第一个成员的地址,*p代表整个结构体

              结构体中的所有成员都 可以取地址&

        4.结构体的存储分配

      struct stu {
      	double f;  //8
      	char s;    //4
      	float f1;   //4
      	int a;     //16
      	long double lf;   //16
      	char c;   //16
      };   //long double为16字节情况下
      

              规则一:每个成员的起始地址,必须根据自身大小边界对齐

              规则二:结构体总大小为最大成员类型长度的倍数 

        5.结构体与函数参数

              当结构体较小时使用值传递,结构体大时使用指针传递

        6.位段

      struct num{
      	unsigned a : 7;   //0~127 
      	unsigned b : 6;   //0~63
      	unsigned c : 19;  //0~(2^19)-1
      };  struct num x;
      

              位段成员必须为int、signed intunsigned int类型,冒号后面为成员占用的位数

        7.联合

      union stu{
      	int a;
      	char b;
      	float c;
      }x ; union stu y;
      

              定义时初始化,必须是第一个成员,且位于花括号内,整个联合体大小为最大成员的长度,包括数组长度

              所有成员共用一块地址,内存中的值面对不同成员时仅仅是解释方式不同

        8.动态内存分配

               C语言头文件stdlib.h中的函数mallocfree用于动态分配内存 

      void *malloc(size_t size); //返回分配的内存起始地址,类型为void*
      void free(void *pointer); //参数必须为malloc分配的地址或NULL
      

      九、文件操作

        1.基本概念

              文本文件:即ASCII文件,1个字符对应1个字节

              二进制文件:将内存中的数据原样输出到磁盘文件

              缓冲文件系统:操作系统为每个正在使用的文件开辟输入输出缓冲区,为减少磁盘的使用次数,待输出缓冲区装满后写入磁盘,输入缓冲区满后读入程序数据区

              文件指针:FILE结构体存储文件的信息,一般使用指针FILE *fp

              系统默认标准流:stdin键盘、stdout显示器、stderr显示器

        2.打开关闭流

              FILE *fopen(char *filename,char *mode);//文件名,打开方式

              成功返回该文件FILE结构体的地址,失败返回NULL

                     r:读取,必须存在(+可以写)

                     w:写入,存在则删除内容,不存在则新建文件(+可以读)

                     a:追加写入,存在则不删除内容,在尾部写入,不存在则新建文件 +可以读)

                     b:打开二进制文件,默认打开文本文件

              int *fclose(FILE *filepointer);//文件指针

        3.文件读写

      只用于标准输入输出函数:getchar()与putchar()、gets()与put()、scanf()与printf()
      用于所有流的函数:
           字符读写函数:fgetc()、getc()和fputc()、putc()
           字符串读写函数:fgets()和fputs()
           格式化读写函数:fscanf()和fprintf()
           二进制读写函数:fread()和fwrite()

        4.字符I/O

              fgetc与fputc是真正的函数,getcputcgetcharputchar是通过#define定义的宏

      int getchar(void);//stdin 
      int fgetc(FILE *stream);
      int getc(FILE *stream);//都返回读到的字符值int类型 ,文件尾EOF也是int类型 
      int putchar(int character);//stdout 
      int fputc(int character,FILE *stream);
      int putc(int character,FILE *stream);//将传入的int裁剪为unsigned char
      

        5.未格式化的IO

               行I/O可以使用两种方式:未格式化和格式化的,都用于操纵字符串 

      char *gets(char *buffer);        //stdin ,无法确定长度的玩具函数
      char *fgets(char *buffer,int buffer_size,FILE *stream);    //换行符/超过缓冲区,停止读取 
      int puts(char const *buffer);    //stdout
      int fputs(char const *buffer,FILE *stream);
      

        6.格式化的IO

      int scanf(char const *format, ...);                //输入源stdin
      int fscanf(FILE *stream,char const *format, ...);    //stream
      int sscanf(char const *string,char const *format, ...);//string(内存中的字符串) 
      int printf(char const *format, ...);                //stdout
      int fprintf(FILE *stream,char const *format, ...);//stream
      int sprintf(char *buffer,char const *format, ...);//送入buffer
      

        7.二进制IO

              二进制输出避免了数值转换为字符串的过程

      size_t fread(void *buffer,size_t size,size_t count,FILE *stream);//buffer输入输出数据缓冲区 
      size_t fwrite(void *buffer,size_t size,size_t count,FILE *stream);//size是每块数据的大小,

        8.定位函数

      int fflush(FILE *stream);//立即将输出缓冲区的数据写入 
      long ftell(FILE *stream);//返回流当前的位置,距离文件起始位置的偏移量
      void rewind(FILE *stream);//将读写指针设置回流的起始位置 
      int fseek(FILE *stream,long offset,int from);//将读写指针设置到距离from的offset偏移处
      //起始SEEK_SET:0、当前SEEK_CUR:1、尾部SEEK_END:2
      

      十、预处理器

        1.#define

               C程序的预处理过程:删除注释、插入#include包含的文件内容、定义和替换#define定义的符号、处理条件编译指令。

      #define name stuff   //将符号name替换为stuff

              name带参数时被称为宏,对任何出现参数的地方应该加() 

        2.条件编译

               以 #if 开始 #endif结束

              条件编译1:选择代码的一部分是否参与编译 

      #if expression  // expression假则忽略编译
      #endif
      

              条件编译2:在编译时选择不同的代码 

      #if expression1  
      #elif expression2 
      #else
      #endif
      

              条件编译3:判断定义是否存在   

      #ifdef name
      #endif
      #ifndef name
      #endif
      

        3.包含

              编译器支持:函数库文件#include<filename>和本地文件#incldue” filename”

              两种方式查找文件路径不同

              头文件重复包含:

      #ifndef _HEADERNAME_H
      #define _HEADERNAME_H
      #endif
      

      十一、标准函数库

      部分函数:
          //算数<stdlib.h> 
      	int abs(int value);//返回参数的绝对值 
      	//浮点数<math.h>
      	double exp(double x);//返回e值的x次幂
      	double log(double x);//返回以e为底x的对数
      	double long10(double x);//返回以10为底x的对数 
      	double pow(double x,double y);//返回x的y次方
      	double sqrt(double x);//返回x的平方根 
      	double abs(double x);//返回x的绝对值
      	//随机数<stdlib.h>
      	int rand(void);//返回0~RAND_MAX之间的伪随机数,可以取模再加偏移得到任意范围 
      	void srand(unsigned int seed);//使用seed参数对随机数发生器初始化,避免相同的随机数
      	//时间<time.h> 
      	clock_t clock(void);//返回程序执行的毫秒数 
      	time_t time(time_t *value);//返回秒数,从1970.1.1开始计算
      	char *ctime(time_t const *value);//将value指向的秒数值,转换为时间字符串
      	struct tm *gmtime(time_t const *value);//将value指向的秒数值,转换为tm时间结构
      

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

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

      抵扣说明:

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

      余额充值