文章目录

  • 概述
  • C源代码各部分说明
  • 定义和声明
  • 编译
  • 数据类型
  • int 整型
  • float/double 浮点数
  • 不同进制的数据表示
  • 常量
  • 整数
  • 浮点型
  • 其他
  • char 字符型
  • bool 布尔型
  • 正负数存储方式
  • 其他类型
  • 字符串
  • 字符串定义
  • 字符串获取和打印
  • 字符串长度计算
  • 字符串函数
  • 修饰符
  • const
  • const用法
  • const作用
  • const常量与#define比较
  • static
  • static用法和作用
  • volitile
  • volitile用法和作用
  • extern
  • 运算符、表达式和语句
  • 运算符
  • 控制语句
  • 函数
  • 函数指针
  • 指针函数
  • 如何得到一个函数对应的函数指针
  • 数组和指针
  • 一维数组
  • 二维数组
  • 指针
  • 如何确定指针类型
  • 如何确定指针所指的类型
  • 内存
  • 内存分区
  • 内存分配
  • 大小端
  • 结构体和其他数据类型
  • 结构体声明
  • 结构体成员的使用
  • 结构体内存对齐
  • 结构体数组
  • 结构体指针
  • 共用体
  • 枚举类型
  • 预处理
  • 位运算


概述

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比较

区别

const

define

编译器处理方式不同

在编译和运行阶段使用

在预处理阶段被展开

类型和安全检查不同

有具体的类型,在编译阶段会检查

没有类型,只是展开

存储方式不同

会在内存中分配

有多少地方使用,就展开多少次

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};
  1. 地址加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. 在头文件中定义:则在包含该头文件的源文件中生效
    注意一个源文件不能引用其他源文件中定义的宏定义。
  1. #include
    #include “stdio.h”和#include <stdio.h>的区别:
  1. 用尖括号:系统到存放C库函数头文件所在的目录中寻找要包含的文件。
  2. 用双引号:系统先在当前用户当前目录中寻找要包含的文件,若找不到,再到存放C库函数头文件所在目录中寻找要包含的文件。
  1. 条件编译
    只对源程序的其中一部分内容在满足一定条件时才进行编译。
    一般形式:
#if 常数表达式
语句段
#else或者#elif 表达式1
语句段
#endif

位运算

  1. &:按位与
  2. |:按位或
  3. ^:按位异或
  4. ~:取反
  5. <<:左移
  6. a<<4:把a的各二进位向左移动4位,高位丢弃,低位补0
    注意别写成<

:右移
a>>4:当a为正数时,最高位补0,而为负数时,符号位为1,最高位是补零还是补1取决于编译系统的规定。