文章目录
- 概述
- C源代码各部分说明
- 定义和声明
- 编译
- 数据类型
- int 整型
- float/double 浮点数
- 不同进制的数据表示
- 常量
- 整数
- 浮点型
- 其他
- char 字符型
- bool 布尔型
- 正负数存储方式
- 其他类型
- 字符串
- 字符串定义
- 字符串获取和打印
- 字符串长度计算
- 字符串函数
- 修饰符
- const
- const用法
- const作用
- const常量与#define比较
- static
- static用法和作用
- volitile
- volitile用法和作用
- extern
- 运算符、表达式和语句
- 运算符
- 控制语句
- 函数
- 函数指针
- 指针函数
- 如何得到一个函数对应的函数指针
- 数组和指针
- 一维数组
- 二维数组
- 指针
- 如何确定指针类型
- 如何确定指针所指的类型
- 内存
- 内存分区
- 内存分配
- 大小端
- 结构体和其他数据类型
- 结构体声明
- 结构体成员的使用
- 结构体内存对齐
- 结构体数组
- 结构体指针
- 共用体
- 枚举类型
- 预处理
- 位运算
概述
C源代码各部分说明
- #:是C的预处理指令,主要作用是在编译器编译前对源代码的准备(预处理)。
- stdio.h:又称为头(head)文件,包含有关例如printf和scanf函数的信息,提供给编译器使用。
- ;分号的作用是声明这一行是C语言的一个语句或指令。
- int是关键字,不能把它作为变量名或函数名
- 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 字符型
- 用来表示字母或其他字符(*、#、%、$等)
- char类型实际存储的是整数而非其他,通过ASCII码规定特定整数对应相应的字母或符号。
- char型变量赋值时,赋值的字符(而非数字)要加上单引号,不加单引号表示该字符是变量(比如我们建立的英文变量名),而加双引号表示其为一个字符串
- 转义字符
bool 布尔型
使用时需要头文件 #include<stdbool.h>
可将bool型变量赋值为true和false
正负数存储方式
正数以原码存储,其原码=反码=补码
负数以补码方式存储,例-127,原码为:1111 1111,反码为:1000 0000,补码为反码+1:1000 0001
其他类型
通过typedef给已存在的类型取别名
字符串
字符串定义
- 可以理解为字符串是以\0作为结束的char型数组,\0是空字符(字符0),注意\0不是数字0,它是非打印字符,其ASCII码值等于0。字符串所占的单元数会比它显示的单元数多1(注意字符串中的空格也占一个字符),因此指定数组大小时要确保数组元素数比字符串长度至少多1。
‘x’一个字符x;“x”两个字符x和\0 - 定义字符串数组
a)char m[]=”limit yourself”;
b)char *ptr = “limit yourself”;
c)char *ptr[5]={“add”,”mul”,”fol”,”sta”,”und”};
字符串获取和打印
- 字符串输出的格式说明符:%s
- gets()
定义: char * gets(char *str); 将读取的字符串保存在形参str中,读取过程中直到出现新的一行为止,其中新的一行的换行字符将会转化为字符串中的空终止符\0; - puts()
定义: int puts(char *str); 形参str用于存放要输出的字符串
作用: 例如: puts(“i love china!”); 输出字符串,遇到’\0’(字符型0)停止 ,并在结尾“自动”进行换行(与printf的不同)
字符串长度计算
strlen()和sizeof()的区别
- sizeof:返回一个对象或者类型所占的内存字节数,注意即使\0是隐藏在字符串末尾的,sizeof也会把它算入,也就是说sizeof不知道何时结束,它会把你要它算的内容全部算出来
- strlen():用于计算“字符串”的长度,以\0结束计数,不把\0算在内,注意空格不是\0,因此strlen会算入空格。
- sizeof是运算符,strlen是函数,strlen包含在string.h头文件中
- sizeof可以用类型做参数,甚至可以用函数做参数; 而strlen只能用char * 做参数,且必须是以“\0”结尾的。
- char str[20]=”0123”;int a=sizeof(str);//a=20 int b=strlen(str);//b=4
字符串函数
大多包含在string.h内,常见函数如下:
- strlen() 计算字符串长度,读到\0结束,计算值不包括\0
- strcat() 例如:strcat(bugs, addon, num ); (bus和addon是字符数组名,num是最多允许添加的字符数),把addon字符串中的内容添加到bugs上,直到加到13个字符或遇到\0为止,可以不加num
- strcmp() 比较两个字符串,如果这两个字符串相同,则返回0
- strcpy()
- strncpy()
- strncmp()
- sprintf()
修饰符
const
const用法
- 修饰局部变量:标识该变量为只读
- 修饰指针:
指针常量,int const *p = &a,重点是常量,即指针所指的内容为只读
常量指针,int * const p = &a,重点是指针,即该指针的值不能修改 - 修饰函数参数
- 修饰函数返回值
- const 类成员函数,则表明其是一个常函数,不能修改类的成员变量
const作用
const修饰变量为只读。
这样做的好处为:
- 保护被修饰的东西,防止意外的修改
- 同时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用法和作用
- 修饰局部变量
该变量只被初始化一次,且如果该变量在函数中没有被修改的话,在函数被多次调用时,该变量的值不变 - 修饰全局变量
声明该变量只能被本模块使用 - 修饰函数
声明该函数只能被本模块内的其他函数所调用 - 修饰类的成员变量
对于该类的所有对象,该变量只存在一份 - 修饰类的成员函数
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;
}
数组和指针
一维数组
- 赋值方式
- 不赋值。未被初始化的元素将被设置为0,例int num[10];
- 使用循环语句对数组逐个赋值
- 赋值各个元素,用逗号隔开。例,int num[4] = {1,2,3,4};
- 地址加1运算
例,int a[4] = {1,2,3,4};
- a+1 : a是a[0]的地址,a+1,跳过1个元素的长度
- &a + 1 : &a是数组a的地址,&a+1,跳过数组a的长度
二维数组
- 赋值
int a[][x] = {{},{},{}};
不能省略列标x - 移动一个元素
&a[0][0] + 1 中n只要小于数组大小即可,及小于i乘j
指针
如何确定指针类型
- int *p[3]
p先和[]结合,说明p是一个数组,然后p[3]与int *结合,说明数组中存放的是指向int类型的指针,结合起来说就是p是存放int类型指针的数组 - int (p)[3]
p先和结合,说明p是一个指针,然后*p与[3]结合,说明指针指向长度为3的数组,然后再和int结合,说明数组里面的元素是int型,结合起来说就是p是指向长度为3的数组的指针。 - int (p)(int)
p先和结合,说明p是一个指针,再与(int)结合,说明指针指向的一个函数,结合起来说就是p是一个指向有一个整型参数且返回类型为整数的函数的指针。
如何确定指针所指的类型
把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。
例如:
int *ptr;//指针指向int 类型
int **ptr;//指针指向int 指针(*)类型
int (*ptr)[3];//指针指向数组大小为3的整型数组
内存
内存分区
一个程序在内存中运行,在内存中被分为如下几个区域
- 代码区:存放程序的代码,即CPU指令,只读
- 常量区:字符串常量,例如char *p = 10,其中10存放在常量区。该区域只读
- 静态区:静态变量和全局变量,直到程序结束之后才会被释放
- 堆:手动申请和释放的区域,malloc申请,free释放
- 栈:函数的参数,函数内部的局部变量,函数调用结束,其中所用的栈空间就被释放
注意区分:
linux进程在内存中被分为如下几个区域:
- BSS段:未初始化的全局变量
- 数据段:已初始化的全局变量,静态变量
- 代码段:存放程序执行代码,以及常量
- 堆:手动申请和释放的区域,malloc申请,free释放
- 栈:函数的参数,函数内部的局部变量,函数调用结束,其中所用的栈空间就被释放
面试时回答其中之一即可
内存分配
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 结构体类型 *指针名
成员引用:
- 通过点运算符引用,注意点运算符优先级高于*,因此用结构体指针引用变量时,要加括号
- 通过指向运算符引用结构成员: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;
}
预处理
- #define 例#define num 4 //注意后面没有分号:“;”
宏定义的生效范围:
宏定义只在它所在的文件中生效。具体分两种情况:
- 在源文件中定义:则只在本源文件中生效
- 在头文件中定义:则在包含该头文件的源文件中生效
注意一个源文件不能引用其他源文件中定义的宏定义。
- #include
#include “stdio.h”和#include <stdio.h>的区别:
- 用尖括号:系统到存放C库函数头文件所在的目录中寻找要包含的文件。
- 用双引号:系统先在当前用户当前目录中寻找要包含的文件,若找不到,再到存放C库函数头文件所在目录中寻找要包含的文件。
- 条件编译
只对源程序的其中一部分内容在满足一定条件时才进行编译。
一般形式:
#if 常数表达式
语句段
#else或者#elif 表达式1
语句段
#endif
位运算
- &:按位与
- |:按位或
- ^:按位异或
- ~:取反
- <<:左移
- a<<4:把a的各二进位向左移动4位,高位丢弃,低位补0
注意别写成<
:右移
a>>4:当a为正数时,最高位补0,而为负数时,符号位为1,最高位是补零还是补1取决于编译系统的规定。