赞
踩
什么是数据类型?
数据类型可以理解为固定内存大小的别名;
数据类型是创建变量的模子(花形的,圆形的,星形的等等);
char
1byteshort
2byteint
4byte内存空间
+----------+
| char c |
+----------+
| short s |
| |
+----------+
| |
| int i |
| |
| |
+----------+
变量也是别名;如int num
告诉编译器申请int大小的内存并取名为num;
变量是一段实际连续存储空间的别名;
程序中通过变量来申请并命名存储空间,通过变量的名字可以使用存储空间;
每一个变量对应的内存空间都有一个编号即地址;
指针也是一种变量,里面存储的是一个普通变量的地址;
即建筑图纸与实际建筑物的关系
#include <stdio.h>
int main() {
char c = 0;
short s = 0;
int i = 0;
printf("%ld, %ld\n", sizeof(char), sizeof(c)); //1,1
// 变量的大小与定义他的类型大小是一样的
// 深刻理解数据类型与变量的关系
printf("%ld, %ld\n", sizeof(short), sizeof(s)); //2,2
printf("%ld, %ld\n", sizeof(int), sizeof(i)); //4,4
return 0;
}
通过打印语句证明变量的本质
#include <stdio.h> typedef int INT32; typedef unsigned char BYTE; typedef struct _demo { short s; // 2 //a=2, @+0 INT32 i; // 4 //a=4, @+4 BYTE b1; // 1 //a=1, @+8 BYTE b2; // 1 //a=1, @+9 } DEMO; typedef struct _test { DEMO de; // 12 char c; // 1 } Test; //16 int main() { INT32 i32; BYTE byte; DEMO d; printf("%ld, %ld\n", sizeof(INT32), sizeof(i32)); // 4, 4 printf("%ld, %ld\n", sizeof(BYTE), sizeof(byte)); // 1, 1 printf("%ld, %ld\n", sizeof(DEMO), sizeof(d)); // 12, 12 printf("%ld\n", sizeof(Test)); // 16 return 0; }
C语言中变量可以有自己的属性,
在定义变量的时候可以加上属性关键字,属性关键字指明变量的特有意义;
auto
是C语言中局部变量的默认属性;static
关键字指明变量的静态属性;static
关键字同时具有作用域限定符的意义,static
修饰的局部变量存储在程序静态区;static
的另外一个意义是文件作用域标示符;
static
修饰的全局变量作用域只是声明的文件中,其他文件不可以访问;static
修饰的函数作用域只是声明的文件中,其他文件不可以调用;
static
修饰全局变量是限定作用域,修饰局部变量是存储在静态区;
static
的意义不是将变量放在静态区,而是限定在当前文件;
register
关键字指明将变量存储在寄存器中;register
只是请求寄存器变量,但不一定请求成功;register
变量必须是CPU寄存器可以接受的值;&
获取register
变量的地址,因为变量不在内存中;register
不能修饰全局变量;register
常用于修饰循环变量;auto
变量存储在程序栈中,默认属性;static
局部变量存储在程序静态区中,static全局变量和函数限定作用文件;register
变量请求存储于CPU寄存器中,不一定成功的;///test2.c//
static int test2_g = 1;
/*
* static int test2_g = 1;
* 变量test2_g将不能在其他文件引用
* static的意义不是将变量放在静态区,而是限定在当前文件
*/
int test2_func(){
return test2_g;
}
///test.c/// #include <stdio.h> int g = 0; int m = 0; //auto int g = 0; // 全局变量存储在全局区,不能分配到随时可变的栈区; // register int m = 0; // 全局变量在程序运行期间都是存在的; // 假如全局变量允许存储在寄存器,那么多个寄存器变量一直占用,CPU的寄存器将无法正常工作; //extern int test2_g; /* static2.c */ //test2_g变量在static2.c中声明为static,因此在此不能引用 extern int test2_func(); /* static2.c */ void f1() { int i = 0; // 局部变量在函数调用结束时被释放 i++; printf("%d\t", i); } void f2() { static int i = 0; // static修饰的变量只会被初始化一次; // static修饰局部变量时,将局部变量存储于静态存储区而不是栈区,不会因为函数运行结束而释放; // static修饰的局部变量作用域没有变化,生命周期被延长; i++; printf("%d\t", i); } int main() { auto int i = 0; /* 明确指明i变量存储于程序栈中 */ register int j = 0; /* 请求存储于CPU中,编译器说我尽量吧 */ static int k = 0; /* 告诉编译器不要使用默认属性分配k,而是存储在静态区 */ for (i = 0; i < 5; i++) { f1(); } //打印5个1 printf("\n"); for (i = 0; i < 5; i++) { f2(); } //打印12345 printf("\n"); // printf("test2_g = %d\n", test2_g); printf("test2_g = %d\n", test2_func()); return 0; }
/test.sh
#! /bin/bash
gcc -o test test.c test2.c
if
语句用于根据条件选择执行语句;else
不能独立存在且总是与它最近的未配对的if相匹配;else
语句后可以接连其他if语句;if (condition) {
//statement1;
} else {
//statement2;
}
if (cond1) {
//statement1;
} else if (cond2) {
//statement2;
} else {
//statement3
}
就是以下形式
if (cond1) {
//statement1;
} else {
if (cond2) {
//statement2;
} else {
//statement3
}
}
bool
型变量应该直接出现于条件中,不需要再比较真假,非0即为真;bool b = TRUE;
if (b) {
//statement1;
} else {
//statement2;
}
int i = 1;
if (0 == i) {
//statement1;
} else {
//statement2;
}
float
型变量不能直接进行0值比较,需要定义精度;#define EPSINON 0.00000001
float f = 0.0;
if ((-EPSINON <= f) && (f <= +EPSINON)) {
//statement1;
} else {
//statement2;
}
switch (表达式) {
case 常量:
代码块
case 常量:
代码块
default:
代码块
}
if
和switch
对比使用实例#include <stdio.h> void f1(int i) { if (i < 60) { printf("Failed!\n"); } else if ((60 <= i) && (i <= 80)) { printf("Good!\n"); } else { printf("Perfect!\n"); } } void f2(char i) { switch (i) { case 'c': printf("Compile\n"); break; case 'd': printf("Debug\n"); break; case 'o': printf("Object\n"); break; case 'r': printf("Run\n"); break; default: printf("Unknown\n"); break; } } int main() { f1(50); f1(90); f2('o'); f2('d'); f2('e'); return 0; }
循环语句的基本工作方式:
通过条件表达式(条件表达式遵循if语句表达式的原则)判定是否执行循环体;
三种循环语句使用对比:
//累加自然数 #include <stdio.h> int f1(int n){ int ret = 0; int i = 0; // 最直观 for(i = 1; i <= n; i++) { ret += i; } return ret; } int f2(int n){ int ret = 0; // 实现方式比较抽象 while((n > 0) && (ret += n--)); // 如果没有n>0的判断就会进入死循环 // while(n && (ret += n--)); //死循环 return ret; } int f3(int n){ int ret = 0; if (n > 0) { // 如果没有n>0的判断就会进入死循环 do { ret += n--; } while(n); // 只要n!=0就是真,while就会继续循环 } // 可以将条件该为while(n > 0);避免死循环; // 用while(n>0) do{} 可以代替上面的分支和循环 return ret; } int main(){ printf("%d\n", f1(10)); printf("%d\n", f2(10)); printf("%d\n", f3(10)); return 0; }
switch能否使用continue关键字?
continue是依赖于循环的,不能用于switch分支;
#include <stdio.h> #include <malloc.h> int func(int n) { // 1.统一的资源分配 int i = 0; int ret = 0; int *p = (int*)malloc(sizeof(int) * n); // 2.代码执行 do { if (NULL == p) break; // break跳出语句块 if (n < 0) break; // 如果将break替换为return,代码将比较繁琐 for (i = 0; i < n; i++) { p[i] = i; printf("%d\n", p[i]); } ret = 1; } while(0); // 外层的do...while语句块一定会执行一次 // 3.统一的资源回收 free(p); return ret; } int main() { if (0 != func(10)) { printf("OK\n"); } else { printf("ERROR\n"); } return 0; }
高手潜规则:禁用goto,程序质量与goto的出现次数成反比;
一般在内核模块的入口函数才会大量使用goto语句,用来处理异常;
goto 语句标签;
其中语句标签是按标识符规定书写的符号,放在某一语句行的前面,标号后加冒号
:
;
语句标签起标示语句的作用,与goto语句配合使用;
C语言中不限制程序中使用标签的次数,但各标签不得重名;
goto语句是改变程序流向,转去执行语句标签所标识的语句;
但是结构化程序中不建议使用goto语句,以免造成程序混乱,使理解和调试程序都产生困难;
示例
#include <stdio.h>
int main(){
int n = 0;
printf("input a string\n");
loop:
if (getchar() != '\n') {
n++;
goto loop;
}
printf("%d\n", n);
return 0;
}
例如输入: hello world
回车打印: 11
goto有可能会造成跳过一些本来应该执行的语句,破坏结构化程序设计顺序执行的规则;
// goto副作用分析 #include <stdio.h> void func(int n) { int* p = NULL; if (n < 0) { //当n>=0时程序就会执行的很好 goto STATUS; //跳过堆内存的分配,使程序崩溃 } // 破坏结构化程序的顺序执行 p = malloc(sizeof(int) * n); STATUS: p[0] = n; } int main() { f(1); f(-1); return 0; }
编译的时候被编译了,但是执行的时候被goto跳过,造成结果的不确定性;
#include <stdio.h> int x = 5; int main () { printf("%p\n", &x); goto a; { // 执行的时候goto会跳过,但仍然会被正常编译 int x = 3; // 又重新申请了一个同名局部变量x printf("%p\n", &x); pritnf("int x = 3\n"); a: // 这里的x都是局部变量 printf("x = %d\n", x); printf("%p\n", &x); } // 以后的x是全局变量 printf("x = %d\n", x); printf("%p\n", &x); return 0; }
void修饰函数返回值和参数
void修饰函数返回值和参数仅为了表示无;
不存在void变量,void v;
编译报错;
没有void的标尺;
void类型的指针是存在的;
c语言规定只有相同类型的指针才可以相互赋值;
void*
指针作为左值用于接收任意类型的指针;void*
指针作为右值赋值给其他指针时需要强制类型转换,才能够赋值给其他类型的指针;
malloc()
返回void*
类型;
int *pI = (int *)malloc(sizeof(int));
char *pC = (char *)malloc(sizeof(char));
void *p = NULL;
int *pni = NULL;
char *pnc = NULL;
p = pI; //ok
pni = p; //oops!
p = pC; //ok
pnc = p; //oops!
void*
指针的使用,实现my_memset()函数;
#include <stdio.h> void* my_memset(void *p, char c, int size) { void *ret = p; char *dest = (char *)p; int i = 0; for (i = 0; i < size; i++) { dest[i] = c; } return ret; } int main(){ int arr[5] = {1, 2, 3, 4, 5}; long num = 9999; char str[10] = "hello"; int i = 0; for (i = 0; i < 5; i++) { printf("%d\t", arr[i]); } printf("%ld\t", num); printf("%s", str); printf("\n"); my_memset(arr, 0, sizeof(arr)); my_memset(&num, 0, sizeof(num)); my_memset(str, 65, sizeof(str) - 1); for (i = 0; i < 5; i++) { printf("%d\t", arr[i]); } printf("%ld\t", num); printf("%s", str); printf("\n"); return 0; }
extern "C" {}
用于告诉编译器用c方式编译;C++编译器和一些变种C编译器默认会按自己的方式编译函数和变量,通过extern关键字可以命令编译器以标准c方式进行编译;
// g++ test.c
#include <stdio.h>
extern "C" {
int add(int a, int b){
return a + b;
}
}
int main(){
printf("res = %d\n", add(2, 3));
return 0;
}
extern用于声明外部定义的变量或函数;
// test2.c
int g = 100;
int get_min(int a, int b) {
return (a < b) ? a : b;
}
//gcc test1.c test2.c
#include <stdio.h>
extern int g; //声明引用外部定义的变量
extern int get_min(int a, int b); // 声明引用外部定义的函数
int main() {
printf("g = %d\n", g);
printf("get_min(3, 5) res %d\n", get_min(3, 5));
return 0;
}
sizeof
是编译器的内置指示符,不是函数;sizeof
用于计算相应实体所占的内存大小,不用运行就可以知道,编译时确定;sizeof
的值在编译期就已经确定;sizeof
不是函数;#include <stdio.h>
int main() {
int a;
printf("%ld\n", sizeof(a));
printf("%ld\n", sizeof a); //sizeof不是函数
printf("%ld\n", sizeof(int));
// printf("%ld\n", sizeof int ); //error: expected expression before ‘int’
C语言中int前面不能出现unsigned/signed/const之外的;
不能是sizeof
类型是不能这么写的;
return 0;
}
用
const int cc = 1;
定义变量后
- 做左值时,将编译报错;
- 做右值时:
- 直接访问
int cb = cc;
直接从变量表取出内容替换;- 间接访问
int *p = (int *)&cc;
在运行时到内存取值后赋值;
#include <stdio.h>
int main(){
const int cc = 1;
int *p = (int *)&cc;
printf("%d\n", cc);
// cc = 3; //编译器报错
*p = 3; //可以间接改变cc的值
printf("%d\n", cc);
return 0;
}
const修饰的数组空间不可被改变(对现在的c编译器而言)
const int arr[5] = {1, 2, 3, 4, 5}; int *p = (int*)arr; int i = 0; for (i = 0; i < 5; i++) { p[i] = 5 - i; //oops! }
- 1
- 2
- 3
- 4
- 5
- 6
int const * p;
//p可变,p指向的普通变量的内容不可变;const int * p;
//p可变,p指向的普通变量的内容不可变,与int const *p等价;int * const p;
//p指针不可变,p指向的普通变量的内容可变;int const * const p;
//p和p指向的普通变量的内容都不可变;const int * const p:
//p和p指向的普通变量的内容都不可变,等价于上句;const * int p;
//不合法const实际上是修饰其左边的东西,const与类型标示符可以更换位置但不能越过
*
号;
口诀
const
相对于*
号,(const在的)左数,(在的右)右指为只读;
- 当const出现在*号左侧时,指针指向的数据为只读;
const char *p = "hello world";
- 当const出现在*号右侧时,指针本身为只读;
const int * func() {
static int count = 0;
count++;
return &count;
}
int const *p = func();
//*p = 3;//报错
//p = NULL;//没问题
int obj = 10;
int a = 0;
int b = 0;
a = obj;
sleep(100);
b = obj;
编译器在编译的时候发现obj没有被当成左值使用,因此会"聪明"的直接将obj替换成10,而把a和b都赋值成10;
volatile int obj = 10;
//编译器将每次都直接访问obj的存储位置
const和volatile是否可以同时修饰一个变量?
可以
const volatile int i = 0;
这个时候i具有什么属性,编译器如何处理这个变量?
const告诉我们不应该通过程序来试图修改i的值;
volatile告诉编译器i的值随时可能会发生改变,每次引用该变量时都要从内存中读取,以获取最新的结果驱动程序中常使用这样的方式;
#include <stdio.h>
struct D {
};
int main() {
struct D d1;
struct D d2;
printf("%d\n", sizeof(struct D));
printf("%d, %0x\n", sizeof(d1), &d1);
printf("%d, %0x\n", sizeof(d2), &d2);
return 0;
}
gcc编译器将空结构体大小定义为0,两个不同的变量有着相同的地址(gcc v7.5中为不同地址);
g++编译器将空结构体大小定义为1,不会有两个具有相同地址的变量;
柔性数组即数组大小待定的数组;
C语言中结构体的最后一个元素可以是大小未知的数组;
C语言中可以由结构体产生柔性数组;
struct soft_array{
int len;
int array[];
}
struct A{
int a;
int b;
int c;
};
union B{
int a;
int b;
int c;
};
int main(){
printf("%d\n", sizeof(struct A)); //12
printf("%d\n", sizeof(union B)); //4
return 0;
}
union的使用受系统大小端的影响
+----------------------+
| 大端格式 |
| int i = 1; |
| 0x0 0x0 0x0 0x1 |
|----------------------|
| 低地址 高地址 |
+----------------------+
+----------------------+
| 小端格式: |
| 低位数据放在低地址 |
| int i = 1; |
| 0x1 0x0 0x0 0x0 |
|----------------------|
| 低地址 高地址 |
+----------------------+
union U {
int i;
char c;
};
union U u;
u.i = 1;
printf("0x%x\n", u.c); //0x1 or 0x01000000??
如果是小端格式,1会存储在低地址,结果返回1
如果是大端格式,1会存储在高地址,结果返回0
char arr[10] = {1,2,3,4,5,6,7,8,9,10};
//从低地址到高地址依次存放01 02 03 04 05 06 07 08 09 10 ...
int *p = (int *)arr;
printf("0x%x\n", *p); // 0x4030201 //这就是小端格式
int isLittleEndian(void) {
union {
int i;
char c;
} u;
u.i = 1;
printf("union int:1, char:0x%08x\n", u.c);
printf("%s endian\n", (u.c == 1) ? "little" : "big");
return (u.c == 1);
}
Unix和网络的字节序都是高字节序;linux是低字节序;
高字节序又叫大端格式,Big endian:将高序字节存储在起始地址
低字节序又叫小端格式,Little endian:将低序字节存储在起始地址
例子:在内存中整数0x01020304的存储方式
内存地址
&4000 &4001 &4002 &4003
LE 04 03 02 01
BE 01 02 03 04
例子:
如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
BE LE
0x0000 0x12 0xcd
0x0001 0x34 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
x86系列CPU都是little-endian的字节序
enum color{
green, //0
red = 2,
blue //3
};
enum color c = green;
printf("%d\n", c); //0
#define
宏定义的常量只是简单的进行值替换,枚举常量是真正意义上的常量;#define
宏常量无法被调试,枚举常量可以;#define
宏常量无类型信息,枚举常量是一种特定类型的常量`建议尽量使用
enum
而不是使用#define
宏;
#define
为简单的字符串替换,无别名的概念;
typedef char* PCHAR;
PCHAR p1, p2; //p1,p2都是char类型的指针;
#define PCHAR char*
PCHAR p3, p4; //就是char *p3, p4; p3是指针,p4是普通的char变量
int *p1, *p2; //p1,p2都是指针
int *p1, p2; //p1是指针,p2是普通变量
C语言中的符号
,.;:?'"()[]{}%^&~-<>!|/#*=+
高手无招胜有招,akari.c,C语言国际混乱大赛最佳展示奖
下面哪些注释是正确的
1 int/*...*/i;
2 char *s="adcdefgh //hijklmn";
3 //Is it a \
valid comment?
4 in/*...*/t i;
/*
,*/
之间的部分将用空格替代;in t i;
/*
,*/
型注释不能嵌套;你觉得y=x/*p
是什么意思
编译器:
将
/*
作为一段注释的开始,把/*
后面的内容当成注释,直到*/
出现为止;
在编译器看来,注释和其他程序元素都是平等的,所以,作为程序员也不能轻看注释.
Note:
写注释不是和人聊天,一定要注意准确有用,避免晦涩和臃肿.
C语言中的续行符\
是指示编译器行为的利器
#include/*hello world */<stdio.h> #def\ ine MAX \ 255 //#define MAX 255 int main() { //\ 这是\ \ 注释 i\ n\ t\ *\ p\ = \ NULL; //int * p= NULL; printf("%p\n", p); return 0; }
宏代码块的定义
#include <stdio.h>
#define SWAP(a,b) \
{ \
int temp = a; \
a = b; \
b = temp; \
}
C语言中的转义字符\
主要用于表示无回显字符,也可以表示常规字符
\n
, \t
, \v
, \b
, \r
, \f
, \\
, \'
, \a
, \ddd
, \xhh
#include <stdio.h>
int main()
{
char *p1 = (char *) 1 ; //p1指向地址为0x1的地方
char *p2 = (char *)'1'; //p2指向'1'所代表的内存地址49
char *p3 = "1"; //p3指向字符串常量"1"
// printf("p1:%s\n", p1); //Segmentation fault
// printf("p2:%s\n", p2); //Segmentation fault
printf("p3:%s\n", p3); //1
// printf('\n'); //fmt='\n', '\n'=10, Segmentation fault
printf("\n"); //ok
return 0;
}
‘a’ 表示字符常量,在内存中占1个字节,‘a’+1表示’a’的ASCII码+1,结果为’b’;
"a"表示字符串常量,在内存中占2个字节,“a”+1表示指针运算,结果指向"a"结束符’\0’
#include <stdio.h>
int main()
{
char c = " ";
while (c=="\t" || c==" " || c=="\n") {
scanf("%c", &c);
} //一次循环也不会
return 0;
}
将字符串赋值给一个字符变量char c == " ";
将发生什么?
" "
在内存中有一个空格和一个’\0’组成,假设空格的地址为0xaabbccdd,由于字符类型只有一个字节,因此会截断将dd赋值给c;c == 0xdd;
"\t"
," "
,"\n"
在内存中也有个具体的地址0x********,与c比较是不可能相等的;将以上程序中d双引号全部替换为单引号就可以实现作者的本意;
- C编译器接受字符串的比较,可意义是错误的(实际是字符串首地址的比较);
- C编译器允许字符串对字符变量的赋值,其意义是可笑的(实际是将字符串首地址截断后赋值给字符变量);
- C编译器不允许将字符变量赋值给字符串,也不允许将字符串直接赋值给字符串,因为字符串实际就是代表这串字符(只读区)的首地址;
清晰基本概念,远离低级错误
逻辑运算符&&
,||
和!
#include <stdio.h>
int main() {
int i = 0;
int j = 0;
if (++i > 0 || ++j > 0) {
printf("%d\n", i); //1
printf("%d\n", j); //0
}
return 0;
}
#include <stdio.h>
int main() {
int i = 0;
int j = 0;
if (++i > 0 && ++j > 0) {
printf("%d\n", i); //1
printf("%d\n", j); //1
}
return 0;
}
||
,&&
从左向右开始计算,当前一个表达式的结果能决定整个表达式的结果,则后面的表达式根本就不会计算或调用;
#include <stdio.h>
int g = 0; //全局变量
int f() {
return g++; //先用后加
}
int main() {
if (f() && f()) { //程序短路,第一个f()被调用并得到0,然后g++;
printf("%d\n", g); //不被执行
}
printf("%d\n", g); //1
return 0;
}
以上代码结果只打印一个1
!
到底是什么#include <stdio.h>
int main() {
printf("%d\n", !0); //1
printf("%d\n", !1); //0
printf("%d\n", !100); //0
printf("%d\n", !-1000); //0
return 0;
}
C语言中的逻辑符!
只认得0,只知道见了0就返回1;非零的都当作真,作用后都返回0;
a?b:c
可以作为逻辑运算符的载体
规则:当a的值为真时,返回b的值,否则返回c的值
#include <stdio.h> int main() { int a = 1; int b = 2; int c = 0; // int *p = NULL; c = a < b ? a : b; //c = 1; //(a < b ? a : b) = 3; //不能做左值; *(a < b ? &a : &b) = 3; //合法 // p = (a < b ? &a : &b); // *p = 3; printf("%d\n", a); printf("%d\n", b); printf("%d\n", c); return 0; }
在C语言中的位运算符
&
按位与|
按位或^
按位亦或<<
左移>>
右移~
按位取反(单目运算符)结合律 a&b&c
<=> (a&b)&c
<=> a&(b&c)
交换律 a&b
<=> b&a
左移和右移注意点
<<
将操作数的二进制位左移,高位丢弃,低位补0;>>
把操作数的二进制位右移,高位补符号位,低位丢弃;
0x1 << 2 + 3
的值会是什么?32,实际上+
,-
运算的优先级高于移位操作
()
来表达计算次序;如何交换两个变量的值
#include <stdio.h> #define SWAP1(a,b) \ { \ int temp = a; \ a = b; \ b = temp; \ } //需要使用额外的变量才可以完成 #define SWAP2(a,b) \ { \ a = a + b; \ b = a - b; \ a = a - b; \ } //a和b很大的时候a+b会溢出 #define SWAP(a,b) \ { \ a ^= b; \ b ^= b; \ a ^= b; \ } // a ^= b; => a=(a^b) // b ^= a; => b=b^(a^b) = a^(b^b) = a^0 => a // a ^= b; => a=(a^b)^a = (a^a)^b = 0^b => b // 该方法不用借助其他变量,也不会溢出,而且运算效率高于普通的数学运算 int main() { int a = 1; int b = 2; SWAP1(a,b); SWAP2(a,b); SWAP(a,b); printf("a = %d, b = %d\n", a, b); return 0; }
假设有一数列A,其中的自然数都是出现偶数次,只有一个自然数出现的次数为奇数次,编程找出这个自然数;
排序太耗时
array[max]
;array[A[i]]++
;空间复杂度太大
考虑到亦或^
位操作及交换律,将所有元素亦或操作即可得到该自然数;
#include <stdio.h>
int main() {
int a[] = {2, 3, 3, 5, 7, 2, 2, 2, 5, 7, 1, 1, 9};
int find = 0;
int i = sizeof(a)/sizeof(a[0]);
while (0 <= --i) {
find ^= a[i];
}
printf("find = %d\n", find);
return 0;
}
&&
,||
,!
与&
,|
,~
的意义是否相同?它们可以在条件表达式中交替使用吗?
不同,一种是逻辑操作,一种是位操作;
1<<32
的结果是什么?
0;
1<<-1
的结果又是什么?
0;
int i = 3;
(++i) + (++i) + (++i);
你有必要这么写吗?
在C语言里面这是一个灰色地带,C语言规范里面只定义了++操作,但也没有规定这样的表达式如何计算;每一种编译器都有自己的处理方式;
int x = 3;
int k = (++x, x++, x+10);
从左到右顺序求值,然后把最后一个表达式的值作为逗号表达式的结果;
前++是先计算再用,后++是先用,表达式结束的时候再自增;
因此结果是k==14;
笔试面试中的++i+++i+++i
不合法
++
,--
表达式的阅读技巧;
编译器就是贪心;
空格可以结束编译器的贪心;
#include <stdio.h> int main() { int i = 0; int j = ++i+++i+++i; // 按照贪心法 // ++i+++i+++i // ++i++ => 2++ => ERROR; // ++i做左值,不可以再后++自增; int a = 1; int b = 2; int c = a+++b; // a++ +b int *p = &a; b = b/*p; // 当编译器读到/时会猜用户可能想做除,然后继续读; // 读到下一个是*,就把后面的都当成注释了; // b = b / *p; //是合法的,空格可以结束编译器的贪心; // 写代码的时候可以适当的使用小括号和空格;可以使代码更美观,也可以适当的防错; return 0; }
() [] -> .
! ~(位取反) ++ -- * & (类型) sizeof
;== !=
)& ^ |
&& ||
?:
#include <stdio.h> #include <malloc.h> typedef struct Demo { int *pInt; float f; } Demo; int func(int v, int m) { return ((v & m) != 0); } int main() { Demo *pD = (Demo*)malloc(sizeof(Demo)); int *p[5]; //int* p[5] int i = 0; i = 1, 2; //i == 1 printf("i:%d\n", i); //i:1 i = (1, 2);//i == 2 printf("i:%d\n", i); //i:2 //*pD.f = 0; //error, 取成员运算符优先级较高 (*pD).f = 0; free(pD); return 0; }
*p.num
;实际是 *(p.num)
.
的优先级高于*
,实际上是对p取偏移,作为指针,然后进行取成员操作;
我们常在程序中用到(*p).num
(等价于p->num
);->
操作符可以消除这个问题;
int *ap[];
实际是int* (ap[]);
[]
的优先级高于*
,实际上ap是个元素为int*
指针的数组,
我们有时候会用到int (*ap)[]
,是一个数组指针;ap指向一个整型数组int a[]
;
int *fp();
实际是int* (fp())
函数
()
优先级高于*
,fp是个函数,返回int*
;
我们有时会用到int (*fp)();
表示一个函数指针,用来指向函数;
(val & mask != 0)
实际是val & (mask != 0)
==
和!=
优先级高于位操作,
我们常会用到(val & mask) != 0
;
c = getchar() != EOF;
实际是c = (getchar() != EOF)
==
和!=
高于赋值操作,特别要注意,这个地方的错误最难发现;
我们常在程序中用到((c = getchar()) != EOF)
类型的,要特别注意优先级问题;
msb << 4 + lsb
实际是msb << (4 + lsb)
算术运算符高于位移运算符,
我们常在程序中用到(msb << 4) + lsb
;
i = 1, 2;
实际是(i = 1), 2;
i = (1, 2);
,将2的结果给i,1,2
表示两个表达式;注意
()
,[]
的优先级最高,然后是.
和->
取成员运算符,然后是后++
后--
;再是其他;
注意后++
,后--
结合的优先级很高,但是要等整个语句运行结束时才会使变量的值生效;
*p1++ = *p2++
即*(p1++) = *(p2++)
;
++
先与p结合,然后再与*
结合;
while ((*p1++ = *p2++) != '\0');
等价于
do {
*p1 = *p2;
p1++;
p2++;
} while (*(p1-1) != '\0');
C语言隐式类型转换
char -> unsigned char ->
short -> unsigned short ->
int -> unsigned int ->
long -> unsigned long -> double
float -> double
#include <stdio.h> int main() { char c = -2; unsigned char uc = 1; printf("c+uc=%hhX\n", c + uc);//FF printf("c+uc=%hX\n", c + uc); //FFFF printf("c+uc=%X\n", c + uc); //FFFFFFFF short s = -2; unsigned short us = 1; printf("s+us=%hX\n", s + us); //FFFF printf("s+us=%X\n", s + us); //FFFFFFFF int i = -2; unsigned int j = 1; if ((i + j) >= 0) { printf("i+j >= 0\n"); //v } else { printf("i+j < 0\n"); } printf("i+j=%X\n", i + j); // FFFFFFFF printf("i+j=%d\n", i + j); // -1 printf("i+j=%u\n", i + j); // 4294967295 // 有符号和无符号类型在内存中的表示都是一样的; // 关键看我们的计算机如何解析; return 0; }
-2
+2: 00000000 00000000 00000000 00000010
-2: 11111111 11111111 11111111 11111101 + 1
-2: 11111111 11111111 11111111 11111110
-2: 0xFFFFFFFE
+1: 00000000 00000000 00000000 00000001
-2+1:
11111111 11111111 11111111 11111111
0xFFFFFFFF
-1
+1 00000000 00000000 00000000 00000001
-1: 11111111 11111111 11111111 11111110 + 1
-1: 11111111 11111111 11111111 11111111
-1: 0xFFFFFFFF
printf("%d", i + j);
,%d
: 以int类型打印0xFFFFFFFF,被解析为-1
printf("%u", i + j);
,%u
: 以int类型打印0xFFFFFFFF,一个很大的正数;
强制类型转换的实现方式是临时生成一个新数据,使用旧数据对新数据进行赋值;
类型转换并不会改变原数据;
int a = 1;
(unsigned char)a == 1;
(short)a == 1;
(int)a == 1;
并不会因为强制类型转换而多读取a所在的存储空间, 只是用原来的数据临时生成了一个新类型的数据供以后解读;
对指针的强制类型转换会影响程序对数据的读取和解析方式;
#include <stdio.h> int main() { char arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; void *p = NULL; p = arr; /* 对指针的强制类型转换会影响程序对数据的读取和解析方式 */ int inta = *(int *)p; short shorta = *(short *)p; char chara = *(char *)p; // 被强制类型转换的数据并没有发生变化 printf("%p, %p, %p\n", (int *)p, (short *)p, (char *)p); // 0x7fff52f3b040, 0x7fff52f3b040, 0x7fff52f3b040 //指针的类型变化,对其解析的方式就会不同,读取对应地址的方式也不同 printf("%x, %x, %x\n", inta, shorta, chara); // 4030201, 201, 1 return 0; }
#include <stdio.h> int main() { char arr[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; void *p = NULL; p = arr; /* 对指针的强制类型转换会影响程序对数据的读取和解析方式 */ printf("%x, %x, %x\n", *(char *)p, *(short *)p, *(int *)p); /* 1, 201, 4030201 */ /* 强制类型转换的实现方式是临时生成一个新数据, */ /* 使用旧数据对新书据进行赋值;以后的操作采用的都是新数据; */ printf("%x, %x, %x\n", (char)arr[0], (short)arr[0], (int)arr[0]); /* 1, 1, 1 */ return 0; }
被编译器隐藏的过程:
file.c + file.h
经过预处理器cpp(删除注释,展开宏等)得到file.i
;file.i
得到file.S
(汇编代码);file.S
得到目标文件file.o
;libc.a
, libc.so
等)得到可执行文件file.out
;#define
删除,并且展开替换所有的宏定义;#if
,#ifdef
,#elif
,#else
,#endif
;#include
,展开被包含的文件;#pragma
指令;gcc -E file.c -o hello.i
gcc -S file.c -o hello.S
汇编器将汇编代码转变为机器可以执行的指令,
每一条汇编语句几乎都对应一条机器指令;
gcc -C file.s -o hello.o
链接器的主要作用是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接;
file1.o file2.o libc.a
--链接器linker-- 得到a.out
;
a.out
文件包含file1.o
,file2.o
,libc.a
中所有的文件;缺点是目标文件较大,
优点是运行较快,可独立运行;
file1.o lib1.so lib2.so
--链接器linker-- 得到a.out
;
a.out中
不包含lib1.so
和lib2.so
文件的内容,只是在加载的时候链接到so文件;优点是so文件可单独维护,编译目标文件较小;
缺点是运行需要加载,运行速度略慢;
#define
定义宏常量可以出现在代码的任何地方;#define
从本行开始,之后的代码都可以使用这个宏常量;#define ERROR -1
#define PI 3.14
#define PATH_0 "D:\cpp\c.ppt"
#define PATH_1 D:\cpp\c.ppt
#define PATH_3 D:\cpp\
c.ppt
以上宏定义都是没有语法错误的;
PATH_3
等价于D:\cpp\c.ppt
宏也可以给一个计算公式起名字;
宏可以使用参数表示计算公式中未知的内容,参数的个数没有限制;
宏的参数可以代表任何东西,所以宏的参数是没有类型的;
带参数的宏是采用二次替换的方式处理的;
用来给计算公式起名的宏中不能定义自己的变量;
#define
表达式给函数调用的假象,却不是函数#define
表达式可以比函数更强大#define
表达式比函数更容易出错#define SUM(a, b) (a)+(b)
#define sum(a, b) ((a)+(b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define DIM(array) {sizeof(array) / sizeof(*array)}
//宏只是单纯的展开替换
printf("%d\n", SUM(1,2) * SUM(1,3)); //1 + 2 * 1 + 3 ==5
printf("%d\n", sum(1,2) * sum(1,3)); //(1 + 2) * (1 + 3) == 12
int i = 1, j = 5;
printf("%d\n", MIN(i++, j)); //返回1,而不是2
int i = 1, j = 5;
printf("%d\n", MIN(++i, j)); //返回3,而不是2
int a[] = {1, 2, 3};
printf("%d\n", DIM(a));
前面三个宏都可以找到替代的函数,最后一个宏是找不到对应的函数的;
/* * 宏macro演示 */ #include <stdio.h> //定义一个宏,即给3.14起一个名字PI #define PI (3.14f) #define CIRCLE(r) (2 * PI * (r)) #define AREAR(r) (PI * (r) * (r)) int main() { int radius = 0; printf("请输入半径: "); scanf("%d", &radius); printf("周长是:%g\n", 2 * PI * radius); printf("周长是:%g\n", CIRCLE(radius)); printf("面积是:%g\n", AREAR(radius)); return 0; }
宏就是简单的文本展开替换
/*
* macro display
*/
#include <stdio.h>
#define SUB1(x, y) (x)- (y)
#define SUB2(x, y) ((x)- (y))
int main() {
printf("SUB1(8, 3) is %d\n", SUB1(8, 3)); //5
printf("21 - SUB1(5, 3) is %d\n", 21 - SUB1(5, 2)); //14,宏展开后违背了预期的优先级导致错误的结果
printf("21 - SUB2(5, 3) is %d\n", 21 - SUB2(5, 2)); //18
printf("SUB2(10, 5-2) is %d\n", SUB2(10, 5 - 2)); //7
return 0;
}
/*
* macro
*/
#include <stdio.h>
#define MUL(x,y) ((x) * (y))
int main() {
printf("MUL(8-2, 9+1) = %d\n", MUL(8 - 2, 9 + 1)); //60
printf("60 / MUL(8-2, 9+1) = %d\n", 60 / MUL(8 - 2, 9 + 1)); //1
return 0;
}
宏只是在编译的预处理阶段进行简单的替换;
如果一个宏里面需要经过复杂的处理才能得到一个结果数字,则这个宏必须写成一个表达式;
不要使用自增或自减的计算结果作为宏的参数;
/*
* macro display
*/
#include <stdio.h>
#define SQUARE(n) ((n) * (n))
int main() {
int num = 4;
printf("SQUARE(num+1) = %d\n", SQUARE(num + 1)); //25
num = 4;
printf("SQUARE(++num) = %d\n", SQUARE(++num)); //36?
num = 4;
printf("SQUARE(num++) = %d\n", SQUARE(num++)); //16?//20
return 0;
}
__FILE__
被编译的文件名__LINE__
当前行号__DATE__
编译时的日期__TIME__
编译时的时间__STDC__
编译器是否遵循标准C语言规范定义日志宏
#include <stdio.h> #include <time.h> // #defien LOG(s) printf("%s:%d %s ...\n", __FILE__, __LINE__, s) #define LOG(s) do { \ time_t t; \ time(&t); \ struct tm *my_tm = localtime(&t);\ printf("%s[%s:%d] %s\n", asctime(my_tm), __FILE__, __LINE__, s); \ } while(0) void f() { printf("Enter f() ...\n"); printf("End f() ...\n"); } int main() { LOG("Enter main() ..."); f(); LOG("Exit main() ..."); return 0; }
#define f (x) ((x) - 1)
上面的宏代表什么意思?
编译器认为这是定义宏
f
代表(x) ((x) - 1)
,实际编译时会报错;
如果是相乘需要加*
号((x)*((x) - 1))
;
如果是要定义宏函数应该写成#define f(x) ((x)-1)
;
宏定义对空格敏感吗?
宏定义会把
#define
之后的第一个字段(遇到第一个空格结束)作为宏,后面部分作为宏实体;
所以宏和实体之间是以他们之间的第一个空格分割的,从这个角度看宏定义对空格是敏感的,但后面的实体部分是可以包含空格的,即对空格不敏感;
宏"调用"对空格敏感吗?
宏只是(用实体部分)展开替换,实体部分对空格不敏感,宏调用处的空格也不敏感;# 条件编译
条件编译可以在编译时只编译某些语句而忽略另外一些语句;
if...else...
是选择性执行;条件编译可以在编译时只编译某些语句而忽略另外一些语句;
#ifdef 宏名称
...
#else
...
#endif
#ifndef 宏名称
...
#else
...
#endif
以上语句可以根据某个宏是否被定义过而从两组语句中选择一组编译;
/*
* 条件编译
*/
#include <stdio.h>
int main() {
//#ifdef ONE
#ifndef TWO
printf("1\n");
#else
printf("2\n");
#endif
return 0;
}
#if 逻辑表达式
...
#elif 逻辑表达式(多个)
...
#else
...
#endif
以上结构可以根据任何逻辑表达式从多组语句中选择一组编译;
#include <stdio.h>
// #define C 1
int main() {
#if (C == 1)
printf("This is 1st printf ...\n");
#else
printf("This is 2nd printf ...\n");
#endif
return 0;
}
也可以在预编译的时候通过-D选项
指定编译的选项
gcc -DC=1 -E test.c -o test.i
#include
的困惑
#include
的本质是将已经存在的文件内容嵌入到当前文件中;#include
的间接包含同样会产生嵌入文件内容的动作;间接包含同一个头文件是否会产生编译错误?
会;
使用条件编译添加头文件宏守卫,可以随心所欲的使用头件;
// global.h
int global = 10;
// test.h
#include <stdio.h>
#include "global.h"
const char* NAME = "Hello world!";
void f() {
printf("Hello world!\n");
}
// test.c
#include <stdio.h>
#include "test.h"
#include "global.h"
int main() {
f();
printf("%s\n", NAME);
return 0;
}
编译报错:
glocal.h:1: error: redefinition of 'global'
glocal.h:1: error: previous definition of 'global' was here
使用单步编译,查看预处理后的文件;可以发现
global.h
被包含了两次;
#ifndef _TEST_H_
#define _TEST_H_
code ;
#endif
例子:
// global.h
#ifndef _GLOBAL_H_
#define _GLOBAL_H_
int global = 10;
#endif
// test.h
#ifndef _TEST_H_
#define _TEST_H_
#include <stdio.h>
#include "global.h"
const char* NAME = "Hello world!";
void f() {
printf("Hello world!\n");
}
#endif
// test.c
#include <stdio.h>
#include "test.h"
#include "global.h"
int main() {
f();
printf("%s\n", NAME);
return 0;
}
条件编译使得我们可以按不同的条件编译不同的代码段,因而产生不同的目标代码;
#if ... #else ... #endif
被预编译器处理;而if...else...
语句被编译器处理,必然被编译进目标代码;
实际工程中条件编译主要用于以下两种情况:
例子:
产品线区分及调试代码应用
#include <stdio.h> // 区分调试版和发布版 #ifdef DEBUG #define LOG(s) printf("[%s:%d] %s\n", __FILE__, __LINE__, s) #else #define LOG(s) NULL #endif void f() { // 区分乞丐版和高级版 #ifdef HIGH printf("This is the high level product!\n"); #else printf("This is the normal product!\n"); #endif } int main() { LOG("Enter main() ..."); f(); // 低配 乞丐版 printf("1. Query Information.\n"); printf("2. Record Information.\n"); printf("3. Delete Information.\n"); // 顶配 高级版 #ifdef HIGH printf("4. High Level Query.\n"); printf("5. Mannul Service.\n"); printf("6. Exit.\n"); #else printf("4. Exit.\n"); #endif LOG("Exit main() ..."); return 0; }
-D
选项能够定义预处理器使用的宏;#error
的用法#error
用于生成一个编译错误消息,并停止编译;
用法:
#error message
注意:message不需要用双引号包围
#error
编译指示字用于自定义程序员特用的编译错误消息;
#warning
用于生成编译警告,但不会停止编译;
#include <stdio.h> #define CONST_NAME1 "CONST_NAME1" #define CONST_NAME2 "CONST_NAME2" int main() { #ifndef COMMAND # warning Compilation will be stoped ... # error undefined Constant Symbol COMMAND #endif printf("%d\n", COMMAND); printf("%s\n", CONST_NAME1); printf("%s\n", CONST_NAME2); return 0; }
gcc -DCOMMAND test.c
#line
的用法#line
用于强制指定新的行号和编译文件名,并对源程序的代码重新编号;
用法:
#line number filename
注: filename可以省略
#line
编译指示字的本质是重定义__LINE__
和__FILE__
一般用于早期开发的调试
#include <stdio.h> void func(); int main() { printf("__func__:%s, %s, %d\n", __func__, __FILE__, __LINE__); //test.c 4 func(); printf("__func__:%s, %s, %d\n", __func__, __FILE__, __LINE__); //test.c 6 return 0; } // 以下代码有long编写 // 以下代码有long编写 // 以下代码有long编写 #line 4 "long.c" // #line 4 "long.c" 的下一行被定义为4行,名字被定义为long.c void func() { printf("__func__:%s, %s, %d\n", __func__, __FILE__, __LINE__);//long.c 6 }
#pragma
预处理#pragma
是编译器指示字,用于指示编译器完成一些特定的动作;#pragma
所定义的很多指示字是编译器和操作系统所独有的;#pragma
在不同的编译器间是不可移植的;
#pragma
指令;#pragma
指令;一般用法:
#pragma parameter
注:不同的parameter参数语法和意义各不相同
#pragma message
message
参数在大多数的编译器中都有相似的实现;message
参数在编译时输出消息到编译输出窗口中;message
可用于代码的版本控制;注意:
message
是VC
特有的编译器指示字,GCC
中将其忽略;
#include <stdio.h> /* #define ANDROID23 1 */ #if defined(ANDROID20) #pragma message("Compile Android SDK 2.0...") #define VERSION "Android 2.0" #elif defined(ANDROID23) #pragma message("Compile Android SDK 2.3...") #define VERSION "Android 2.3" #elif defined(ANDROID40) #pragma message("Compile Android SDK 4.0...") #define VERSION "Android 4.0" #else #error Compile Version is not provided! #endif int main() { printf("%s\n", VERSION); return 0; }
#pragma pack
内存对齐Alignment
数据对齐会造成结构体内部不同成员变量之间有空隙;
a=min(n, max(self))
普通类型取#pragma pack(n)
、自身大小,二者最小值;a=min(n, max(sizeof element...))
结构体类型取#pragma pack(n)
、最大子变量,二者最小值;n为系统对齐参数,一般默认为4,可以通过
#pragma pack()
类宏自定义
Completion
c=max(A)
即所有成员的对齐参数A的最大值(一定不会超过pack值,试证明之);// x * // y y // z * typedef struct { char c1; //s=1, a=min(4,1), @+0;对齐参数a=1; short s; //s=2, a=min(4,2), @+2;对齐参数a=2; char c2; //s=1, a=min(4,1), @+4;对齐参数a=1; } ST1; //6 = n*2, c=max(a)==2;补齐参数c=2 // x x y y // y y y y // z z z z // m * * * typedef struct { short s; //s=2, a=min(4,2), @+0;对齐参数a=2; ST1 st1; //s=6, a=min(4,max(1,2,1)), @+2;对齐参数a=2; int i; //s=4, a=min(4,4), @+8;对齐参数a=4; char c; //s=1, a=min(4,1), @+12;对齐参数a=1; } ST7; //16 = n*4, c=max(a)==4;补齐参数c=4;
a=min(n, max(self))
;a=min(n, max(sizeof element...))
;c=max(A)
;结构体中子变量的顺序会影响结构体的大小,占用空间小的子变量写前边可以节约内存空间;
struct test1 {
char c1; // 1
short s; // 2
char c2; // 1
int i; // 4
}; //12
struct test2 {
char c1; // 1
char c2; // 1
short s; // 2
int i; // 4
}; //8
test1和test2两种类型所占用的内存空间是否相同?
#include <stdio.h> struct test1 { char c1; short s; char c2; int i; }; //12 struct test2 { char c1; char c2; short s; int i; }; //8 int main() { printf("%d, %d\n", sizeof(struct test1), sizeof(struct test2)); //12, 8 return 0; }
为什么需要内存对齐?
- CPU对内存的读取是不连续的,而是分成块读取的,块的大小值可以是1,2,4,8,16字节;
- 当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣;
- 某些硬件平台只能从规定的地址处取某些特定类型的数据,否则抛出硬件异常;
#pragma pack能够改变编译器的默认对齐方式
#pragma pack(n) //设置编译器按照n个字节对齐,n可以取1,2,4,8,16
#pragma pack() //默认4字节对齐
#pragma pack(push) //将当前的对齐字节数压入栈顶,不改变对齐字节数
#pragma pack(push,n) //将当前的对齐字节数压入栈顶,并按照n字节对齐
#pragma pack(pop) //弹出栈顶对齐字节数,不改变对齐字节数
#pragma pack(pop,n) //弹出栈顶并直接丢弃,按照n字节对齐
#pragma pack(push,1) //可以指定结构的对齐和补齐的字节数 #pragma pack(pop) //恢复push前的值
- 1
- 2
- 3
#include <stdio.h> #pragma pack(push,2) struct test1 { char c1; short s; char c2; int i; }; // 10 struct test2 { char c1; char c2; short s; int i; }; // 8 #pragma pack(pop) int main() { printf("%d, %d\n", sizeof(struct test1), sizeof(struct test2)); // 10, 8 return 0; }
#include <stdio.h> //假设存储位置都起始于0 #define OFFSET_OF(type, member) ((size_t)(&((type *)0)->member)) #define OO1(t, m1) #m1,OFFSET_OF(t, m1) #define OO2(t, m1, m2) OO1(t, m1), OO1(t, m2) #define OO3(t, m1, m2, m3) OO2(t, m1, m2), OO1(t, m3) #define OO4(t, m1, m2, m3, m4) OO3(t, m1, m2, m3), OO1(t, m4) #define OO5(t, m1, m2, m3, m4, m5) OO4(t, m1, m2, m3, m4), OO1(t, m5) #define SHOW_OO1(t, m1) \ printf("\n%s offset: %s:%ld\n", #t,OO1(t,m1)) #define SHOW_OO2(t, m1, m2) \ printf("\n%s offset: %s:%ld, %s:%ld\n", #t,OO2(t,m1,m2)) #define SHOW_OO3(t, m1, m2, m3) \ printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld\n", #t,OO3(t,m1,m2,m3)) #define SHOW_OO4(t, m1, m2, m3, m4) \ printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld\n", #t,OO4(t,m1,m2,m3,m4)) #define SHOW_OO5(t, m1, m2, m3, m4, m5) \ printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld, %s:%ld\n", #t,OO5(t,m1,m2,m3,m4,m5)) #pragma pack(8) //#pragma pack(4) //#pragma pack(2) typedef struct S1 { short a; // 2, a=min(pack, 2) long b; // 8, a=min(pack, 8) } S1; typedef struct S2 { char c; // 1, a=min(pack, 1) S1 d; // 2+8,a=min(pack, max(2,8)) double e; // 8, a=min(pack,8) } S2; #pragma pack() int main() { printf("%ld, %ld\n", sizeof(S1), sizeof(S2)); SHOW_OO2(S1, a, b); SHOW_OO3(S2, c, d, e); return 0; }
运行结果
#pragma pack(8)
16,32; 0,8; 0,8,24;#pragma pack(4)
12,24; 0,4; 0,4,16;#pragma pack(2)
10,20; 0,2; 0,2,12;#
#
运算符用于在预编译期将宏参数转换为字符串
#include <stdio.h>
#define CONVERS(x) #x
int main(){
printf("%s\n", CONVERS(Hello world!));
pritnf("%s\n", CONVERS(100));
printf("%s\n", CONVERS(while));
printf("%s\n", CONVERS(return));
return 0;
}
#
运算符在宏中的妙用#include <stdio.h>
#define CALL(f, p) (printf("Call function %s\n", #f), f(p))
// 打印一句call function,然后再调用函数
int square(int n) {
return n * n;
}
int f(int x) {
return x;
}
int main() {
printf("1. %d\n", CALL(square, 4));
printf("2. %d\n", CALL(f, 10));
return 0;
}
##
##
运算符用于在预编译期粘连两个符号
#include <stdio.h>
#define STR(n) #n
#define LOCAL(n) woshiqianzui_##n
int main(){
printf("STR(abc) is %s\n",STR(abc));
int woshiqianzui_num = 10;
int LOCAL(num1) = 20;//与上一句等效,可以方便书写
printf("%d\n",woshiqianzui_num);
printf("%d\n",LOCAL(num1));
return 0;
}
利用##
定义结构体类型
#include <stdio.h> #define STRUCT(type) typedef struct _tag_##type type;\ struct _tag_##type STRUCT(Student) { char* name; int id; }; int main() { Student s1; Student s2; s1.name = "s1"; s1.id = 0; s2.name = "s2"; s2.id = 1; printf("%s\n", s1.name); printf("%d\n", s1.id); printf("%s\n", s2.name); printf("%d\n", s2.id); return 0; }
请继续阅读后半部分:
C语言深度剖析笔记2 https://blog.csdn.net/halazi100/article/details/125844545
C语言深度剖析笔记2
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。