C语言知识梳理

概述

C源代码各部分说明

  1. #:是C的预处理指令,主要作用是在编译器编译前对源代码的准备(预处理)。
  2. stdio.h:又称为头(head)文件,包含有关例如printf和scanf函数的信息,提供给编译器使用。
  3. ;分号的作用是声明这一行是C语言的一个语句或指令。
  4. int是关键字,不能把它作为变量名或函数名
  5. num是标识符,也就是变量或函数的名字。

定义和声明

定义:编译器创建一个对象,为这个对象分配一块内存并给它取一个名字,即变量名或对象名。
声明:1、告诉编译器,这个名字已经匹配到一块内存上了。2、告诉编译器,这个名字已经预定了,不能在别的地方用作变量名或对象名。

定义声明
int i;extern int i;
定义创建了对象并分配内存声明没有分配内存

编译

程序的编译过程:
预处理-》编译-》汇编-》链接
预处理:用于将所有的#include头文件以及宏定义替换成其真正的内容
编译:将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程
汇编:将上一步的汇编代码转换成机器码(machine code),这一步产生的文件叫做目标文件
链接:将多个目标文以及所需的库文件(.so等)链接成最终的可执行文件(executable file)

数据类型

int 整型

根据编译器的不同,int型所占字节数会不同,在VC里面一个整型占4个字节

Int型默认是有符号的(有负号),及signed

整数溢出:超过最大值时,会继续回到起点数值,从这里开始计数。例如:short型NUM(-32768—32767),NUM=32767,NUM+1=-32768,NUM+2=-32767.

float/double 浮点数

Float:至少6位有效数字;double:至少10位有效数字

浮点数存储方式:将一个数分为小数部分和指数部分,并分别存储

注意:浮点数只能是实际值的近似,例如float型的mun,则mun不可能等于0(或其他整数),只能为-0.00001或0.00001,则用mun作比较判断时,例mun为0时作为条件,则该条件应写为0.00001>mun>-0.00001

浮点型:10.99给整型a,则a=10,注意这里不进行四舍五入

不同进制的数据表示

常量

常量前缀:八进制:0(数字0),十六进制:0x或0X(数字0)

常量后缀:八进制和十六进制数据,在C里面默认是认为他们是int型,但是有时希望一个小一点的数是long型的,这要通过后缀实现,加L:例如032L,加U:表示该数是无符型的:032LU

整数

打印整型的八、十六、十进制数据:显示数字是类型用%o、%x、%X(注意是字母o(octal),且必须是小写),要显示数据的完整格式(例如021,0x15等),则可添加#说明符,例如%#o,%#x。

打印short/long/unsigned类型的数据:short:%hd(显示十进制short型数据),long:%ld/%lo/%lx,unsigned:%ud/%uo/%ux。注意:格式说明符要使用小写字母,此外,虽然%d也可以来表示long型的数据,但是这不利于程序在不同系统中的移植(不同系统可能long型和int型长度不一样)。

浮点型

%f:单精度float的显示
%lf:双精度double的显示
%Lf:long double 型的显示

其他

%e:用指数形式显示数字
%p:显示地址

char 字符型

  1. 用来表示字母或其他字符(*、#、%、$等)
  2. char类型实际存储的是整数而非其他,通过ASCII码规定特定整数对应相应的字母或符号。
  3. char型变量赋值时,赋值的字符(而非数字)要加上单引号,不加单引号表示该字符是变量(比如我们建立的英文变量名),而加双引号表示其为一个字符串
  4. 转义字符

bool 布尔型

使用时需要头文件 #include<stdbool.h>
可将bool型变量赋值为true和false

正负数存储方式

正数以原码存储,其原码=反码=补码
负数以补码方式存储,例-127,原码为:1111 1111,反码为:1000 0000,补码为反码+1:1000 0001

其他类型

通过typedef给已存在的类型取别名

字符串

字符串定义

  1. 可以理解为字符串是以\0作为结束的char型数组,\0是空字符(字符0),注意\0不是数字0,它是非打印字符,其ASCII码值等于0。字符串所占的单元数会比它显示的单元数多1(注意字符串中的空格也占一个字符),因此指定数组大小时要确保数组元素数比字符串长度至少多1。
    ‘x’一个字符x;“x”两个字符x和\0
  2. 定义字符串数组
    a)char m[]=”limit yourself”;
    b)char *ptr = “limit yourself”;
    c)char *ptr[5]={“add”,”mul”,”fol”,”sta”,”und”};

字符串获取和打印

  1. 字符串输出的格式说明符:%s
  2. gets()
    定义: char * gets(char *str); 将读取的字符串保存在形参str中,读取过程中直到出现新的一行为止,其中新的一行的换行字符将会转化为字符串中的空终止符\0;
  3. puts()
    定义: int puts(char *str); 形参str用于存放要输出的字符串
    作用: 例如: puts(“i love china!”); 输出字符串,遇到’\0’(字符型0)停止 ,并在结尾“自动”进行换行(与printf的不同)

字符串长度计算

strlen()和sizeof()的区别

  1. sizeof:返回一个对象或者类型所占的内存字节数,注意即使\0是隐藏在字符串末尾的,sizeof也会把它算入,也就是说sizeof不知道何时结束,它会把你要它算的内容全部算出来
  2. strlen():用于计算“字符串”的长度,以\0结束计数,不把\0算在内,注意空格不是\0,因此strlen会算入空格。
  3. sizeof是运算符,strlen是函数,strlen包含在string.h头文件中
  4. sizeof可以用类型做参数,甚至可以用函数做参数; 而strlen只能用char * 做参数,且必须是以“\0”结尾的。
  5. char str[20]=”0123”;int a=sizeof(str);//a=20 int b=strlen(str);//b=4

字符串函数

大多包含在string.h内,常见函数如下:

  1. strlen() 计算字符串长度,读到\0结束,计算值不包括\0
  2. strcat() 例如:strcat(bugs, addon, num ); (bus和addon是字符数组名,num是最多允许添加的字符数),把addon字符串中的内容添加到bugs上,直到加到13个字符或遇到\0为止,可以不加num
  3. strcmp() 比较两个字符串,如果这两个字符串相同,则返回0
  4. strcpy()
  5. strncpy()
  6. strncmp()
  7. sprintf()

修饰符

const

const用法

  1. 修饰局部变量:标识该变量为只读
  2. 修饰指针:
    指针常量,int const *p = &a,重点是常量,即指针所指的内容为只读
    常量指针,int * const p = &a,重点是指针,即该指针的值不能修改
  3. 修饰函数参数
  4. 修饰函数返回值
  5. const 类成员函数,则表明其是一个常函数,不能修改类的成员变量

const作用

const修饰变量为只读。
这样做的好处为:

  1. 保护被修饰的东西,防止意外的修改
  2. 同时const常量可以节省空间,避免不必要的内存分配,const常量在定义时不分配空间,而是在对其引用时才分配一次,而宏定义在每一次展开都会分配一次内存,例如:
     #define PI 3.14159;    //宏常量
     const double Pi = 3.14159;   //此时并未将Pi放入RAM中
     double i = Pi;   //此时为Pi分配内存,以后不再分配
     double I = PI;   //编译期间进行宏替换,分配内存
     double j = Pi;   //没有内存分配
     double J = PI;   //再进行宏替换,又一次分配内存

const常量与#define比较

区别constdefine
编译器处理方式不同在编译和运行阶段使用在预处理阶段被展开
类型和安全检查不同有具体的类型,在编译阶段会检查没有类型,只是展开
存储方式不同会在内存中分配有多少地方使用,就展开多少次

static

static用法和作用

  1. 修饰局部变量
    该变量只被初始化一次,且如果该变量在函数中没有被修改的话,在函数被多次调用时,该变量的值不变
  2. 修饰全局变量
    声明该变量只能被本模块使用
  3. 修饰函数
    声明该函数只能被本模块内的其他函数所调用
  4. 修饰类的成员变量
    对于该类的所有对象,该变量只存在一份
  5. 修饰类的成员函数
    a. 静态成员函数不能访问普通成员变量,可以访问静态成员变量
    b. 静态成员函数可以通过类名来调用,例如:

#include <iostream>

#include <string>

using namespace std;

class test
{
private:

    static int m_value;		//定义私有类的静态成员变量
public:

    test()
    {
    	m_value++;
    }

    static int getValue()		//定义类的静态成员函数
    {
    	return m_value;
    }
};

int test::m_value = 0;		//类的静态成员变量需要在类外分配内存空间

int main()
{
    test t1;

    test t2;

    test t3;

    cout << "test::m_value2 = " << test::getValue() << endl;	//通过类名直接调用公有静态成员函数,获取对象个数

    cout << "t3.getValue() = " << t3.getValue() << endl;		//通过对象名调用静态成员函数获取对象个数

    system("pause");
}
/*
结果为:
test::m_value2 = 3
t3.getValue() = 3
*/

volitile

volitile用法和作用

被volatile类型定义的变量,系统每次用到它时,都直接从对应的内存中提取,而不会使用相应寄存器中的数值。

extern

extern修饰变量或者函数,用来标识该变量或者函数在别的文件中进行了定义,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

运算符、表达式和语句

运算符

优先级由高到低:

()
-,+,++,--,sizeof(type)\\所有一元运算符,-/+是正负号
*,\,%
+,-
<,>,<=,>=
==,!=
=

特殊运算符:
问号运算符:<表达式1>?<表达式2>:<表达式3> 表达式1成立—执行表达式2;表达式1不成立—执行表达式3

控制语句

分支型:if—else;switch
循环型:for;while;do-while
辅助控制型:break,continue;goto;return
break:1 在循环中使用,会跳出本层循环;2 在switch中使用,是跳出switch语句
continue: 结束本次循环,开始下次循环

函数

函数指针

是一个指针,该指针指向一个函数

指针函数

是一个函数,该函数返回一个指针

如何得到一个函数对应的函数指针

例如函数定义: void fun(int)
将函数名fun换为()即可得到调用该函数的指针类型,那么类型是void () (int) ,给它加上名字即可:void (*p)(int)
使用说明:

#include <stdio.h>

void func(int tmp)
{
    printf("get num is :%d", tmp);
    return;
}
int main()
{
    void (*p)(int);
    p = func;
    p(10);
    return 0;
}

数组和指针

一维数组

  1. 赋值方式
    1. 不赋值。未被初始化的元素将被设置为0,例int num[10];
    2. 使用循环语句对数组逐个赋值
    3. 赋值各个元素,用逗号隔开。例,int num[4] = {1,2,3,4};
  2. 地址加1运算
    例,int a[4] = {1,2,3,4};
    1. a+1 : a是a[0]的地址,a+1,跳过1个元素的长度
    2. &a + 1 : &a是数组a的地址,&a+1,跳过数组a的长度

二维数组

  1. 赋值
    int a[][x] = {{},{},{}};
    不能省略列标x
  2. 移动一个元素
    &a[0][0] + 1 中n只要小于数组大小即可,及小于i乘j

指针

如何确定指针类型

  1. int *p[3]
    p先和[]结合,说明p是一个数组,然后p[3]与int *结合,说明数组中存放的是指向int类型的指针,结合起来说就是p是存放int类型指针的数组
  2. int (p)[3]
    p先和
    结合,说明p是一个指针,然后*p与[3]结合,说明指针指向长度为3的数组,然后再和int结合,说明数组里面的元素是int型,结合起来说就是p是指向长度为3的数组的指针。
  3. int (p)(int)
    p先和
    结合,说明p是一个指针,再与(int)结合,说明指针指向的一个函数,结合起来说就是p是一个指向有一个整型参数且返回类型为整数的函数的指针。

如何确定指针所指的类型

把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。
例如:

int *ptr;//指针指向int 类型
int **ptr;//指针指向int 指针(*)类型
int (*ptr)[3];//指针指向数组大小为3的整型数组

内存

内存分区

一个程序在内存中运行,在内存中被分为如下几个区域

  1. 代码区:存放程序的代码,即CPU指令,只读
  2. 常量区:字符串常量,例如char *p = 10,其中10存放在常量区。该区域只读
  3. 静态区:静态变量和全局变量,直到程序结束之后才会被释放
  4. 堆:手动申请和释放的区域,malloc申请,free释放
  5. 栈:函数的参数,函数内部的局部变量,函数调用结束,其中所用的栈空间就被释放

注意区分:
linux进程在内存中被分为如下几个区域:

  1. BSS段:未初始化的全局变量
  2. 数据段:已初始化的全局变量,静态变量
  3. 代码段:存放程序执行代码,以及常量
  4. 堆:手动申请和释放的区域,malloc申请,free释放
  5. 栈:函数的参数,函数内部的局部变量,函数调用结束,其中所用的栈空间就被释放
    面试时回答其中之一即可

内存分配

malloc 函数 :void *malloc(unsigned int size); 该函数在内存中的堆区分配一块size大小的内存空间。
malloc 函数会返回一个指针,该指针指向分配的内存空间,如果出现错误则返回NULL。
使用malloc使注意free,否则会出现内存泄漏
free 函数 :void free(void *ptr); 该函数是使用由指针ptr指向的内存区,使部分内存区能被其他变量使用,ptr是最近一次调用calloc或malloc函数时返回的值。最后将指针指向NULL。

char *ptr = malloc(x);
/* 
  ··· 
*/
free(ptr); 
ptr = NULL;

大小端

对于十六进制数0x1234
地址在内存中是按从低到高存放,利用指针指向该数,如果打印出来12说明是大端,如果是34是小端。
低序字节存储在起始位置:小端;高序字节存放在起始地址:大端

#include <stdio.h>

int main()
{
    short data = 0x1234;
    char *p = (char *)&data;
    if((*p) == 0x12)
        printf("big\n");
    else
        printf("small\n");
        
    return 0;
    
}

结构体和其他数据类型

结构体声明

struct	结构体名
{
成员列表;
};
struct 结构体名 结构体变量名;

结构体成员的使用

对结构体成员的引用:结构体变量名.成员名 或 结构体变量名->成员名
没有结构体指针变量出现时用”.”,当有结构体指针变量出现时用—>

结构体内存对齐

如果结构体中的最大基本类型是大于等于4字节,则按照4字节对齐。
如果结构体中的最大基本类型是小于4字节,按照最大的那个类型对齐。

结构体数组

#include <stdio.h>
#include <string.h>

struct number
{
  char name[12];
  int old;
};
int main()
{
    struct number student[10];
    //或者memcpy(student[0].name, "sunshuai", 12);
    strcpy(student[0].name, "sunshuai");
    student[0].old = 12;
    
    printf("student[0].old = %d\n", student[0].old);
    printf("student[0].name = %s\n", student[0].name);
    
    return 0;
}

结构体指针

定义结构体指针:struct 结构体类型 *指针名
成员引用:

  1. 通过点运算符引用,注意点运算符优先级高于*,因此用结构体指针引用变量时,要加括号
  2. 通过指向运算符引用结构成员:pStruct->成员名,注意没*号;->优先级同点运算符,比点运算符略低。
#include <stdio.h>
#include <string.h>
struct number
{
  char name[12];
  int old;
};
int main()
{
    struct number student[10];
    //strcpy(student[0].name, "sunshuai");
    memcpy(student[0].name, "sunshuai", 12);
    student[0].old = 12;
    printf("student[0].old = %d\n", student[0].old);
    printf("student[0].name = %s\n", student[0].name);
    
    struct number *p = &student[0];
    printf("(*p).old = %d\n", (*p).old);    //法1
    printf("(*p).name = %s\n", (*p).name);
    
    struct number *q = &student[0];
    printf("q->old = %d\n", q->old);    //法2
    printf("q->name = %s\n", q->name);
    
    return 0;
}

共用体

使几种不同类型的变量存放到同一段内存单元中,所有成员共用一段内存。共用体在同一时刻只能有一个值,它属于某一个数据成员,共用体的大小就等于最大成员的大小。
定义:

#include <stdio.h>
union number
{
    int a;
    int b;
    char c;
};
int main()
{
    union number my1;
    my1.a = 1;
    printf("my1.a = %d\n", my1.a);
    printf("my1.c = %d\n", my1.c);
    return 0;
}

枚举类型

定义:enum 枚举名 { 枚举常量表 };
一个枚举变量包含一组相关的标识符,其中每个标识符都对应一个整数值,称为枚举常量。
上例中Red对应数值0,其他常量依次加1。可以单独给某一枚举常量赋值,紧随其后的标识符对应加1。

#include <stdio.h>
enum COLOR {RED, GREEN, YELLOW};
int main()
{
    int r  = RED;
    int g  = GREEN;
    int y  = YELLOW;
    return 0;
}

预处理

  1. #define 例#define num 4 //注意后面没有分号:“;”
    宏定义的生效范围:
    宏定义只在它所在的文件中生效。具体分两种情况:

    1. 在源文件中定义:则只在本源文件中生效
    2. 在头文件中定义:则在包含该头文件的源文件中生效
      注意一个源文件不能引用其他源文件中定义的宏定义。
  2. #include
    #include “stdio.h”和#include <stdio.h>的区别:

    1. 用尖括号:系统到存放C库函数头文件所在的目录中寻找要包含的文件。
    2. 用双引号:系统先在当前用户当前目录中寻找要包含的文件,若找不到,再到存放C库函数头文件所在目录中寻找要包含的文件。
  3. 条件编译
    只对源程序的其中一部分内容在满足一定条件时才进行编译。
    一般形式:

#if 常数表达式
语句段
#else或者#elif 表达式1
语句段
#endif

位运算

  1. &:按位与
  2. |:按位或
  3. ^:按位异或
  4. ~:取反
  5. <<:左移
  6. a<<4:把a的各二进位向左移动4位,高位丢弃,低位补0
    注意别写成<
  7. :右移
    a>>4:当a为正数时,最高位补0,而为负数时,符号位为1,最高位是补零还是补1取决于编译系统的规定。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值