编码基本功(三):C语言知识点

本文详细介绍了C语言的关键概念,包括变量与数值表示(包括符号范围、内存分配和指针),数制表示,条件编译,位运算,结构体与枚举,typedef的自定义类型,内存分配策略,数组与字符串处理,以及C文件操作。同时涵盖了时间复杂度分析、64位整型输入输出和随机数生成等内容。
摘要由CSDN通过智能技术生成

C

目录

C语法

变量与数值表示

  • 变量范围 有符号 − 2 N − 1 -2^{N-1} 2N1 2 N − 1 2^N-1 2N1,无符号0到 2 N − 1 2^N-1 2N1。例如char是一个字节,则范围为-128到127,而unsigned char也是一个字节,范围为0到255
  • 寄存器类型(register),这类变量保存在CPU的一个寄存器中,其访问速度较普通变量快。很少见
  • volatile类型
    • volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:
      volatile int i=10;
      int a = i;
      ...
      // 其他代码,并未明确告诉编译器,对 i 进行过操作
      int b = i;
      
      /* volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问*/
      
    • 一般说来,volatile用在如下的几个地方:
      • 中断服务程序中修改的供其它程序检测的变量需要加 volatile;
      • 多任务环境下各任务间共享的标志应该加 volatile;volatile不能用于线程间的同步
      • 存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能由不同意义
    • 参考链接
  • static变量
    • 在整个程序运行期间都不释放,但作用域不变,也就是说相比全局变量,假如在函数范围内定义一个静态变量,在函数外它是不可使用的,这是它与全局变量不同的地方。静态变量只会初始化一次(编译时),假设多次调用某个定义了静态变量的函数,则该变量也只初始化一次。
    • 静态变量如不初始化,会自动初始化为0.
数制表示
  • C中无二进制表示方法;
  • 八进制常量必须以数字0开头;
  • 十六进制整常量必须以数字0加X或x开头;
  • aEn或aen 表示 a 1 0 n a10^n a10n
  • 整数后加l表示长整形,加u表示无符号整数,加LL便是long long类型,加f表示浮点型(l、u、f大小写均可);如058L、35u、0xA5LU、356f、2LL

条件编译

  • #if 常量表达式(注意这里不能使用变量和含有sizeof等在编译时求值的操作符) #elif #else #endif
  • #ifdef #else #endif ;
  • #ifndef #else #endif,注意(a)(b)没有elif选项。也可放在函数中内使用。

位运算

  • 按位异或 a^b ; a|b(或) ; a&b(与) ; ~a(取反) ; >> ,<< (右移及左移)有的系统移入0,有的系统移入1。移入0的称为“逻辑移位”,即简单移位;移入1的称为“算术移位”。
    例: a的值是八进制数113755 , 用二进制形式表示为1001011111101101
        a>>1: 0100101111110110 (逻辑右移时)
        a>>1: 1100101111110110 (算术右移时)
        在有些系统中,a>>1得八进制数045766,而在另一些系统上可能得到的是145766。Turbo C和其他一些C编译采用的是算术右移,即对有符号数右移时,如果符号位原来为1,左面移入高位的是1。
    

运算优先级

  • 只有三个优先级是从右至左结合的,它们是单目运算符、条件运算符(?:)、赋值运算符(=也是,*=, /=等),。其它的都是从左至右结合。a=b=5,then a=5;
  • 单目运算符> 算术运算符 > 关系运算符 > && > ||( ! 是单目运算符) > 赋值运算符(=也是)。 [ ]++的运算优先级都比*(指针)高 。&运算优先级比 !=
  • *的运算优先级只比[ ]( ),点.->-++--

结构体

  • 结构体可以在函数内部声明,也可以在外部。外部声明的可以为所有函数所使用,函数内部声明的只能在本函数内使用。
  • 初始化结构体数组 struct stu boy[2]={{2010,”John”,19},{2011,”Amy”,20}};
  • 结构体指针ps=&boy; ps->num (*ps).numboy.num等价
  • 字节对齐(自身(自身对齐值和指定对齐值中小的那个值)和结构体(其成员中自身对齐值最大的那个值和指定对齐值中小的那个)), 记住是双小就行了

枚举enum

  • 主要应用于需要返回几个状态,但如果写成0,1,2会使得代码不够清晰,这时用枚举,enum STATUS{GOOD,BAD},然后当函数返回时便返回GOOD或BAD. 除此之外,当只有某几个状态时,都可以使用enum。
  • enum中的值是从0开始的,然后下面的逐次加一, 也可以设置任意一个元素的值,后面的元素就在这个元素基础上加1

typedef

  • 自定义变量名:typedef 原类型名 新类型名;
  • typedef struct pts{ int x; int y; }Point;// Point 不是结构体变量的名称,而是类型名称
  • typedef struct pts *pPoint ; 然后声明某些指针,可以编写 pPoint pfirst;*
  • typedef int (*func_pointer)(int, int ); 然后利用func_pointer pfun; 来声明一个这样的函数指针。
  • typedef char Name[10]; then, Name a ; is equivalent to char a[10] ;

C内存分配

  • 分配后要检查是否分配成功(指针是否为空); 需要把返回值强制转换为指针类型;动态内存时方后指针要置为空。
  • calloc() 与 malloc() 的一个重要区别是:calloc() 在动态分配完内存后,自动初始化该内存空间为零,而 malloc() 不初始化,里边数据是未知的垃圾数据。
  • void *realloc(void *p, unsigned int size)用于改变原来通过maloc函数或colloc函数分配的存储空间,即将p指向的存储空间改为size个字节。如果原先的内存大小后面还有足够的空闲空间用来分配,加上原来的空间大小就=newsize 大小的。如果原先的内存大小后面没有足够的空闲空间用来分配,那么从堆中另外找一块newsize大小的内存。并把原来大小内存空间中的内容复制到newsize中。返回新的mem_address指针

数组

  • 数组赋初值:在给数组赋初值时,如果初值的个数小于数组元素的个数,则剩余的元素自动取值为0,无论几维都一样。在无需赋初值的情况下定义数组时最好这样定义int a[SIZE]={0}; int a[size][SIZE]={0}; 但对于字符数组,初值有几个只给相应元素赋初值,其余不做处理
  • C语言中数组按行存放的。例:a[4][4]内存中a[0][3]后一个元素是a[1][0]
  • 数组名可以作为数组的首地址,从而a[i]等价于*(a+i) ; char a[n]; char *p=&a[0]; 等价于char *p=a
二维数组
  • 二维数组初始化int a[2][2]={{1,3},{4,5}}int a[2][2]={1,3,4,5} 两者等价
  • 对于二维数组a[m][n] ; aa[0]&a[0][0]的数值相同,但它们并不是相的东西
  • 访问数组元素的指针表达式,自行百度

字符串

  • 字符串数组char s[20]=“I love c” ;
  • 使用指针表示字符串:如果用数组表示字符串,程序代码如下:char s[10]=”Hello” ; 这里对于s数组不能使用s=”12345” 来改变s,只能使用strcpy(s,“12345”) .但如果使用指针则为 char *ps=”Hello” , 如果要改变ps则直接使用ps=”1234”, 但不能使用strcpy
  • 备忘
    • gets 碰到空格仍旧读取,scanf("%s",&str) 则遇到空格就停止。getchar().
    • 字符串常量内存字节数等于字符串中字符数加1,后面加 ‘\0’。
    • strlen计算的是不包括 ’\0’ 的长度。
    • 将字符串转化为数值(头文件stdlib.h):atof(), atoi(), atol(), atoll().

指针

  • 指针基础: &i表示i的地址,int *p=&i或者 int *p ; p=&i , 然后(*p)就代表变量i . int表示指针所指向变量的类型。
  • 空指针:为了保证良好的程序设计风格,任何无效的指针都应设为空指针NULL,指针在定义时要将其初始化为NULL
  • 常量指针和指针常量
    1. int const* p 或 const int* p , 不可通过指针修改变量的值,但可以修改指针指向的地址值
    2. 指针常量 int* const p2 , 不可修改指针的地址值,但可以修改指针指向变量的值
    3. const int* const p3 地址不可改,指向的值也不可通过指针修改
  • 指针运算pi += 1,指针加一的含义是指针向后移动一个元素。实际上这里指针(int类型)所指向的绝对地址变化了4,若指针指向的类型为double,则每次指针加一,其地址数据变化为8. 简而言之,指针加1表示向后移动一个元素,而非表示地址值加1。减法同理。
    • 指向不同类型的指针不能相互赋值,但可以通过强制转换类型将一种指针类型转换为另一种指针类型。long d ;char *p ;p=(char*)&d ;注意只能将&d而非指针进行类型转换。转换后p指针加1就指向d后一个字节,而非后一个元素,即地址值加1而非加4。
  • 指向数组的指针(数组指针)int a[5] , (*pa)[5] ; pa=&a (pa=&a[0] is wrong) , 访问元素时 *p[0](*p)[0], p[0]得到的是地址 ,这里pa++表示加5个元素, 20个字节 称为行指针int *pi=&a[0], then a[3]=0 is equivalent to (*pa)[3]=0 or pi[3]=0 or *(pi+3)=10 ,注意pipa是不同的。
  • 指针数组*pb[4] 这个数组的每一个成员(加*)都是指针,当需要通过pb里某个元素的指针来访问它所指向的元素时,使用*pb[1], 如果是pb[1]得到的是一个地址。
    int main()
    {
     int c[4] = { 1, 2, 3, 4 };
     int *a[4]; //指针数组
     int(*b)[4]; //数组指针
     b = &c;
     //将数组c中元素赋给数组a
     for (int i = 0; i<4; i++)
     {
        a[i] = &c[i];
     }
     //输出看下结果
     printf("%d\n",*(a[1])); //输出2就对
     printf("%d\n",(*b)[2]);
     system("pause");
     return 0;
    }
    
  • 行指针和列指针int a[3][4] , int (*pb)[4] ; pb=&a[0] ; for(i=0;i<3;i++) (*pb++)[i]=i*i ; 从而a[0][0]=0 , a[1][1]=1 , a[2][2]=4 . 之所以这样乃是pb是包含4元素的数组,pb加1后将指向a[1]数组。pb被称为行指针int *pa,pa=&a[0][0], 为列指针
  • 函数指针
    • 函数指针即为指向函数的指针变量,可以通过函数指针间接调用函数。int max(int a , int b) ; int (*pf1)(int a , int b) ; pf1=max or pf1=&max; 可将函数指针作为形参,达到调用多个函数的目的。
      int sum(int, int);
      
      int main(void)
      {
          int a = 10, b = 5 , result = 0;                   
          int (*pfun)(int, int); pfun = sum; result = pfun(a, b);            
          printf("result = %d", result); 
      }
      
      // 还有函数指针数组
      int (*pfun[3])(int, int); pfun[0] = sum; pfun[1] = product; pfun[2] = difference;
      
  • 指针函数
    • 函数的返回类型为指针,常见于各类字符串处理函数。格式:函数类型 *函数名(形参表),例:char * max(char a , char*p), return p(return p1);调用的时候就是pr=max(a,d); 然后*pr代表返回值。
  • 指向指针的指针
    void p2p(int **pp, int *addr){ **pp = 3; *pp = addr; }
    int main()
    {
        int a = 1; int b = 2;; int *p = &a; int *pb = &b
        p2p(&p, pb);
        printf("a: %d, b:%d\n", a, b); //结果为 a = 3 , b =2
        *p = 4;
        printf("a: %d, b:%d\n", a, b); // 结果为 a = 3 , b = 4
        return 0;
    }
    
  • 无类型指针(void *):它是指向无类型数据的指针,也就是说它可以指向任何类型的数据,何类型的指针都可以直接赋值给它(但在C++中这种转换在C++中并不是双向的,在不使用强制转型的前提下,不允许将void*赋给其他类型的指针,比如:如下代码在C语言中是有效的(但不建议这样写):从void*隐式转换为double* double* pDouble=malloc(nCount*sizeof(double));但要使其在C++中正确运行,就需要显式地转换:double *pDouble=(double*)malloc(nCount*sizeof(double));),无须强制转型。
  • **指针与数组的等价表达 **, 自行百度

C文件

  • FILE *fp; if((fp=fopen("c:\\zhe.txt","w"))==NULL){ exit(0); } fclose(fp);
  • 打开文件选项
    • rt或r 只读打开一个文本文件,只允许读数据;
    • wt或w 只写打开或建立一个文本文件,只允许写数据;
    • at或a 追加打开一个文本文件,在文件末尾写数据。
    • rt+或r+ 读写打开一个文本文件,允许读和写。wt+或w+,at+或at同理;
    • rb,wb,ab,rb+,wb+,ab+适用于二进制文件,与上述类似。
  • C文件操作相关API,自行百度

备忘

浮点数与0不可通过等于来比较

时间复杂度对比

  • O(1)<O( l o g 2 n log_2n log2n)<O(n)<O( n l o g 2 n nlog_2n nlog2n)<O( n 2 n^2 n2)<O( n 3 n^3 n3)<O( 2 n 2^n 2n)<O(n!)<O( n n n^n nn

size_t

  • size_t 是无符号整形
  • 设计 size_t 就是为了适应多个平台的 。size_t的引入增强了程序在不同平台上的可移植性。size_t是针对系统定制的一种数据类型。为了提高代码的可移植性,就有必要定义这样的数据类型。一般这种类型都会定义到它具体占几位内存等,经测试发现,在32位系统中size_t是4字节的,而在64位系统中,size_t是8字节的,这样利用该类型可以增强程序的可移植性。
  • 打印size_t 类型的值时要小心。这是无符号值,如果选错格式说明符,可能会得到不可靠的结果。推荐的格式说明符是%zu。不过,某些情况下不能用这个说明符, 作为替代,可以考虑%u 或%lu

代码备忘

获得0到limit的随机数
  • int num; srand(time(NULL)) ; num=rand( )%limit
输入输出重定向到文件
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
fclose(stdout);调试时只有输入这条语句后,结果才会显示内容
如何读取和显示64整形
a) signed 64-bit integer,__int64,scanf("%I64d", &x),printf("%I64d", x);
b) Unsigned 64-bit integer,unsigned __int64,scanf("%I64u", &x); printf("%I64u", x);
c) 也可以写成long long 和unsigned long long 但可能一些编译器不支持,倘若C++的话,那直接cin>>x, cout<<x (有些编译环境不支持)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值