1.什么是预编译?
预编译又称预处理,是整个编译最先做的工作,及程序执行前的一些预处理工作
主要处理开头的指令,如拷贝 #include包含的文件代码、替换#define定义的宏、条件编译等
何时需要预编译? 总是使用并不经常改动的大型代码体
2. # 和 ##的作用?
#是宏参数 字符串替换运算符,## 是把两个 宏参数 连接起来的运算符
#define STR(arg) #arg //STR(hello) ---> hello
#define NAME(x,y) x##y //NAME(1,2) ---->12
3.static 的用法
修饰局部变量,该变量(栈区)变成静态存储类型,该变量存在于内存的静态区,作用域不变。
修饰全局变量,该变量的作用域,只能在该文件中使用。不能在其他文件中通过extern引用
修饰函数, 静态函数只能在申明它的源文件中使用
4. extern 关键字的作用
用来修饰变量或函数,表明该变量或函数都是在别的文件中定义的,提醒编译器在其他文件中寻找定义
5. sizeof 的用法
计算对象或类型所占的内存字节数
sizeof 求得的结构体(及其对象)的大小,不等于各个数据成员大小之和
sizeof运算符 和函数strlen的区别与用法
sizeof()计算分配空间的字节数
strlen()计算空间中的字符个数(不包括'\0')
char str[20] = {"hello world"};
int a[10];
sizeof(str); //20 sizeof(a); //4*10 strlen(str); //11
6.结构体的赋值
C语言中对结构体变量的赋值##在初始化##或者在定义后按字段赋值
1. struct dog{ char a; int b; }x={ 'a' ,2 };
2.定义结构体变量 struct dog x ={ 'a' ,2 };
按字段赋值
struct dog x; x.a='a'; x.b =2;
7.结构体变量如何比较?
int memcmp( const void *s1, const void *s2, size_t n) //n 为比较长度
按字节来比较,如果超过数组长度,比较不会停止'\0' 之后继续比较下去,可能会出错
strcmp() 按字符比较,直到出现结果
结构体成员数组大小为0,好处是可以在结构体中分配不定长的大小
typedef struct st{
int a;
int b;
char c[0]; //空数组
} st_t;
sizeof(st_t) == 8; //即 c[0] ==0
8. malloc/free 与new/delete 的区别
1.malloc/free 是C/C++语言的标准库函数,new/delete 是C++的运算符,都是动态分配和释放内存。2.主要区别就是 new/delete 对象在创建和释放的时候会执行构造函数和析构函数
9.C++里的类 和C里面的 struct 区别
C++ 中的类具有成员保护功能,并且具有继承和多态的特点
C里面没有成员函数,可以有函数指针,不能继承和派生,但可以结构体嵌套
10.主函数的写法
int main(void) {}
int main( int argc, char *args[] ){}
argc 运行程序执行的参数个数 , 第一个是程序本身 xx.out
args 指向字符串数组的指针 xx.out ...
11.printf();
int a=10, b=20, c=30;
printf("%d %d %d\n",a+b+c, (b=b*2), (c=c*2) ); // 110 40 60 输出从右往左
float double 的输出 %.2f --- 输出两位小数,会自动进行四舍五入
%8.2f --- 8是输出宽度,就是数据输出后在终端上占据8个 英文字母,不足的补空格
float a= 13.141592f;
printf("%d\n", a ); //输出随机数,从内存里随机取来输出,不进行类型转换
12. float x 与 "零值 "的比较
const float ESP = 0.000001f; //参考量
float x = 0.0f;
if( x >= -ESP && x <= ESP ){ yes } else { no }
指针 int *p =NULL;
if(p == NULL ){}
13.操作符优先级问题
char *ptr = "Linux"; //在内存中占了 6个字节 '\0'
printf( "%c\n", *ptr++ ); // L 同一级,自右向左 ++*ptr ==M 编译报错,L常量不允许修改
printf("%c\n", *ptr); // i
书写时多加(),
伪运算符 优先级最高()
单目大于双目 三目
算符运算符 > 比较运算符 > 位运算符 > 逻辑运算符
自右向左:只有单目(++ --) 三目 赋值运算符
三目运算: a?b:c?d:e 就是 a?b : (c?d:e ) //运算从左向右,结合从右向左
14.逻辑短路问题 : 有逻辑运算参与
int a=5,b=6,c=7,d=8,m=2,n=2;
(m=ad); //m=1 ||或 为真所以右边的不会执行 n的值不变
printf("%d\t%d\n",m,n); //1 2
(m=ad); //true && fause m=1 n=0
(n=c>d)||(m=a
(n=c>d)&&(m=a
应用:
if(strlen(name)!=0 && strcmp(name,"admin") ==0 ){} 保证了程序的健壮性
//name ==NULL 后面就不会执行
递归终结条件
int test(int n,int *sum)
{
n && test(n-1,sum);
return *sum += n;
}
int ret =0;
test(100,&ret); //1+2+3.....+100
如果说不能使用循环,那么第一个应该想到 递归 重视逻辑短路问题
15.分支
int a=4;
switch( a>5 ){ case 0: ...} //a>5 判断表达式 假0 case 0会被执行
switch( 3.14 ) //报错 不能放 float double
//只能放整型(数) ---char bool short long 枚举
对于switch 控制表达式应该是整数类型,case也一样
并且 case表达式转换后的值,不应该是相同的值 'a' ==97
switch(a-4){ //2 普通表达式
case 0: ; break;
case 1: ; break;
case 2: ; break;
default: //other
printf("");
}
16.二维数组和一维数组的相互转化
将一维数组中的元素分别存入二维数组中
int a[9] ={1,2,3,4,5,6,7,8,9};
int b[3][3];
(1).代码实现:
int i, j, k=0;
for(i=0; i<3; i++){ //控制行
for(j=0; j<3; j++){ //控制列
b[i][j]=a[k]; k++;
}
}
(2).借助边界
for(i=0; i<9; i++){ //控制数组a
b[0][i] = a[i]; //赋值
}
(3).运算:
for(i=0; i<9; i++){
b[i/3][i%3] =a[i];
}
二维数组转化为一维数组:
int array1[A][B] ={1,2,3,4}; int array2[A*B];
for(i=0; i for(j=0; j array2[k] = array1[i][j]; //将二维数组的值一个个存入一维数组中
k++;
}
}
17.字符串
strcpy() 和memcpy()的区别??
strcpy() 字符串拷贝,自动拷贝一个'\0'
memcpy(char *dst, const char *src, size_t len ); 内存拷贝
区别:
strcpy()只能使用在字符串上,会自动拷贝'\0'
memcpy() 适用于各种类型,必须要指定长度
char name[10];
gets(name); //当 name length ,超过10,内存越界,可能出错
18.指针
char a; //开辟一个字节的空间
char *str =&a;
strcpy(str, "hello"); //6 个字节的空间,越界 可能导致程序崩溃
内存分区:
栈区:函数参数 局部变量 系统管理(释放 回收)
堆区:程序员管理(malloc/free)作用域 生命周期
*静态存储区:全局变量 静态数据(static) 常量
bss段:未初始化的全局变量、静态变量
data段:初始化全局变量 静态变量 常量
代码区:程序代码
int main()
{
char *str = "abcd"; //str--栈区 "zbcd"--静态数据区-常量 不允许更改
str[2] = 'f'; //程序崩溃 段错误--试图去修改不属于你的内存空间
}
int *p;---->malloc(p); ----->free() ----->p= NULL;
自己开辟的空间free之后一定要记得置空---清理
数组和指针相结合?/
int a[5] = {1,2,3,4,5};
int *ptr =(int *)(&a+1); //&a[0] --取的是整个数组,指向数组尾部
printf("%d %d\n", *(a+1), *(ptr-1) ); //2, 5
a[0] 第一个元素的首地址, 第一个字节 ,用首地址来表示整个数组
19.不定长函数参数
(传参1,传参2,...)
printf(const char *format, ...); //也是一个不同的函数,可以传不同的参数,打印出来
// ... 可以传任意长度的参数---变长参数
va_start(va_list arg_ptr ,prev_param);
//以固定参数的地址作为起点,确定变长参数的内存起始地址,获取第一个变长参数的首地址
va_arg(va_list arg_ptr,type); //获取每个变长参数 返回的参数类型
va_end(va_list arg_ptr); //将va_list 类型的arg_ptr置0
20.const 不可改变的变量
char const *p1;
const char *p2; //p1 and p2可以指向不同的空间,指向的变量的内容不能改变
char *const p3; //不可以指向其它空间,可以修改里面的内容
21.结构体
//#pragma pack(n) //宏定义 指定对齐模数n 必须是2的整数倍
字节对齐
第一原则:
对齐模数 32位机器---4字节 64位机器---8字节
第二原则:
按照对齐模数和 结构体中最大的数据类型大小比较 ,以比较小的那个数据大小进行对齐
第三原则:
结构体中第一个数据放在 offset 为0的位置,每个数据,存储的起始位置,必须是自身大小的整数倍
struct {
short a;
short b;
short c;
}C; sizeof(C) ==6;
struct {
char a;
short b; //2
int c; //1*11 1111
}d; sizeof(d)==8; 四字节对齐 cpu取数据 一次取四个字节(char short)
struct {
char a;
int c; //1*** 1111 11**
short b; //2
}f; //12 四字节对齐 cpu取数据 int要取两次 ,被拆开不符合规则
struct {
char a;
short b;
char c;
int d; //4
}e; //sizeof(e) ==12
22.联合体和大小端问题
union xx{ //联合体整个空间是 最大成员在空间中的大小
short a;
char b[sizeof(a) ]; //short 在不同的编译器中所占的字节不同
} kk;
if(sizeof(a) ==2){
kk.a =0x1234; //看这个数据字这片空间 里是怎样存储的就知道大小端
if(kk.b[0] ==0x12 ){
printf("big"); //高字节存储在低地址
}else {
printf("small");
}
}
23.C语言文件操作
文件拷贝,实现一个函数
int copy_file(const char *srcFilename, const char desFilename)
{
//打开文件 r r+ rw w+
FILE *fp =fopen(srcFilename , "r");
if(fp<0){
printf("%s没有打开成功\n",srcFilename);
return -1;
}
FILE *fp1 =fopen(desFilename , "w");
if(fp<0){
printf("%s没有打开成功\n",desFilename);
return -1;
}
/*
fgetc() fputc()
fgets() fputs()
fscanf() fprintf() 格式化写
fread() fwrite() 二进制写
*/
char ch = '\0';
while(EOF != (ch =fgetc(fp) )){ //文件结束 =EOF
fputc(ch, fp1);
}
fclose(fp); fclose(fp1);
fp =NULL; fp1=NULL;
return 0;
}
24.代码改错题
int main(int argc, char *argv[] )
{
char *ptr = (char *)malloc(10);
if(NULL ==ptr ){
printf("\nmalloc failed\n");
exit(1); //return -1;
}else if(argc ==1 ){
printf("\n Usage \n");
}else{
memset(ptr, '\n', 10);
strnpy(ptr, argv[1],9);
while( *ptr != 'z'){
if(*ptr ==' ') break;
else ptr++; //指针的指向改变了
}
if(*ptr =='z'){
printf("stream contained 'z'\n");
}
free(ptr); //zhhgg--没问题 freez释放不属于自己的空间 ,段错误
}
}
25.exit() 的用法
exit(num);
根据 num的值,知道程序退出的原因
num =0 正常退出
!0 不正常退出
int main(int argc, char *argv[])
{
if(argc ==1){
exit(1);
}else{
exit(0);
}
return 0;
}
脚本:exit.sh
gcc exit.c -o exit
./exit
echo $?
./exit asd
echo $? ## $?程序退出的返回码
system("touch aa"); //终端创建一个文件 aa
system("rm -rvf aa"); //删除 aa
atexit(func); //func 函数名/函数指针 --当退出的时候
每次调用 exit();的时候,都会去调用这个传入的函数func
一般用来作为程序结束前的清理工作
注:
传入的函数,必须是无参数的
可以传入32个,调用的顺序和传入的顺序相反
_exit(1)
26.函数返回值
int fun(int val)
{
int a =val;
a++;
return &a; //局部变量的地址,函数结束,这片内存就销毁了
}
int main()
{
int a = 10;
int *val = fun(a); //可能会出现段错误
}
// 多文件编程 全局变量(同名变量)不可以定义在多个.c 文件中
//static 修饰 变成静态变量,模块化只能在本文件中使用
作用域大的值和作用域小的值同名,则会使用作用域小的值
27. 代码改错题
void strcopy(void)
{
char *src = "hello world";
char *dest =NULL;
int len =strlen(src); //不包含'\0',计算的是有效字符的长度
dest = (char *)malloc(len+1); //多开辟一个字节大小的空间 malloc(len)
char *d = dest;
char *s = &src[len-1]; //指向'\0' 前的一个字符(有效字符) src[len]
// '\0'字符串的结束标志
while(len-- != 0)
{
*d++ = *s--; //直接指针赋值不允许 d++ = s--;
}
*d = '\0';
free(dest);
dest =NULL;
}
28.将小写字母转换成大写字母
void upperCase(char str[] )
{
int i=0; //sizeof(str)==8 ---指针大小的长度
//越界
for(i=0; i
29.内存分配和二级指针
堆 heap ---(空闲表)--有空间就可以申请,使用完free就放回这个--空闲表
栈 stack
char *p1;
int main()
{
char s[] = "abc"; //栈区--数组
char *p = "123456"; //p 栈区 "123456" 常量数据区
p1 = (char*)malloc(10); //堆区
strcpy(p1, "123456"); //常量数据区
}
二级指针:指向指针的指针
如何在函数中更改一级指针呢?
传二级指针
void change(int **a, int **b)
{
*a= *b; //更改一级指针
}
int main()
{
int a=5,b=6;
int *c= &a,*d = &b;
change(&c, &d);
printf("%d\n", *c); //6
}
30.联合(共用)体
联合体的大小--取决于它所有的成员中占用空间最大的一个成员的大小
union u1{
double a;
int b;
}; //8
union u2{
char a[13];
int b;
}; //16 ---int 四字节对齐
union u3{
char a[13];
char b;
}; //13 一字节对齐
不仅仅union,对于别的复合数据类型,如union,struct ,class 的对齐方式为成员中最大的成员--对齐方式
可以使用 #pragma pack(x),可以改变编译器的对齐方式
31.int (*a[10])(int) 的含义
从右向左,由远及近,括号优先
a是一个数组,里面存放的是指针,所存的指针是指向有一个int形参的函数,函数的返回值是int型。
32.如何让程序跳转到 0地址执行?
C语言程序跳转需要调用函数 /函数指针
typedef void (*Fun_t)(void)
Fun_t fun =(Fun_t)0; //初始化函数指针
fun();
33.带参宏和带参函数的区别
主要是看它们处理的时间,参数类型,程序长度,运行时间。
带参宏,编译时展开,没有参数类型,程序变长,不占运行时间
带参函数,运行时处理,参数类型需定义,函数调用和返回时耗费时间
34.volitile 的用法--易变的
修饰变量,告诉编译器不要缓存到寄存器,每次直接读取该地址
用途:1.并行设备的硬件寄存器 (如:状态寄存器)
2.一个中断服务子程序中会访问到非自动变量
3.多进程中会被几个任务 共享的变量
这是我自动总结的,难免会有出错的
如果有什么不对的,欢迎在评论区留言