一、const和volatile分析
1、const修饰变量
# 在c语言中const修饰的变量时只读的,其本质还是变量
# const修饰的变量会在内存占用空间
# 本质上const只对编译器有用,在运行时无用
原来const不是真的常量
2、const修饰数组
# 在C语言中const修饰的数组是只读的
#const修饰的数组空间不可以被改变
const int A[5] = {1, 2, 3, 4, 5}
int *p = (int *)A;
int i = 0;
for(i=0; i<5; i++)
{
p[i] = 5-i;//报错
}
3、const修饰指针
# const int *p; //p可变,p指向的内容不可变
# int const *p; //p可变,p指向的内容不可变
# int *const p; //p不可变,p指向的内容可变
# const int *const p; //p不可变, p指向的内容不可变
口诀:左数右指
当const出现在*号左边时指针指向的数据为常量,当const出现在*后右边时指针本身为常量
const修饰函数参数和返回值
# const修饰函数参数表示在函数体内不希望改变参数的值
# const修饰函数返回值表示返回值不可改变,多用于返回指针的情形
const int * func()
{
static int count = 0;
count++
return &count;
}
深藏不漏的volatile
1、volatile可理解为“编译器警告指示字”
2、volatile用于告诉编译器必须每次去内存中去取变量值
3、volatile主要修饰可能被多个线程访问的变量
4、volatile也可以修饰可能被未知因数改变的变量
int obj = 10;
int a = 0;
int b = 0;
a = obj;
sleep(100);
b = obj;
以上代码,编译器在编译的时候发现obj没有被当成左值使用,因此会“聪明”的直接将obj替换成10,而把a和b都赋值为10.
课后思考
# const和volatile是否可以同时修饰一个变量?
# const volatile int i = 0; 这个时候i具有什么属性?编译器如果处理这个变量?
答:可以。这个时候i具有const和volatile的双重属性。i变量不可以在编译过程中被程序代码修改,同时编译器不得对i进行优化编译。
二、struct与union分析
1、空结构体占用多大内存?
#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;
}
使用g++编译后结果为
1
1,22ff4f
1,22ff4e
使用gcc编译后运行结果为
0
0,22ff50
0,22ff50
可以看出使用gcc编译结果,不合理,因为d1和d2是相同的地址,而两个变量占有相同的地址这是不可思议的。
而g++似乎合理。除此之外,我们还可以看到,对于不同的编译器,它们往往对空的结构体有不同的定义,所以我们尽量不要定义空结构体,避免这个C语言的灰色地带。
由结构体产生柔性数组
1、柔性数组即数组大小待定的数组
2、C语言中结构体的最后一个元素可以是大小未知的数组
3、C语言中可以由结构体产生柔性数组
struct SoftArray
{
int len;
int array[];
};
柔性数组的实例分析:
#include <stdio.h>
#include <malloc.h>
typedef struct _soft_array
{
int len;
int array[];
}SoftArray;
int main()
{
int i = 0;
SoftArray* sa = (SoftArray*)malloc(sizeof(SoftArray) + sizeof(int) * 10);
sa->len = 10;
for(i=0; i<sa->len; i++)
{
sa->array[i] = i + 1;
}
for(i=0; i<sa->len; i++)
{
printf("%d\n", sa->array[i]);
}
free(sa);
return 0;
}
柔性数组的使用:存储斐波拉次数列
思维导图:
代码示例:
#include <stdio.h>
#include <malloc.h>
typedef struct soft_array
{
int len;
int array[];
}Soft_Array;
Soft_Array * creat_soft_array(int size)
{
Soft_Array *ret = NULL;
if(size > 0)
{
ret = (Soft_Array*)malloc(sizeof(Soft_Array) + sizeof(int) * size);
ret->len = size;
}
return ret;
}
void fac(Soft_Array *ret)
{
if(ret != NULL)
{
int length = ret->len;
if(1 == length)
{
ret->array[0] = 1;
}
else
{
ret->array[0] = 1;
ret->array[1] = 1;
if(2 < length)
{
for(int i=2; i<length; i++)
ret->array[i] = ret->array[i-1] + ret->array[i-2];
}
}
}
}
int del_soft_array(Soft_Array *SA)
{
free(SA);
}
int main()
{
int size = 10;
Soft_Array* ret = creat_soft_array(size);
fac(ret);
for(int i=0; i<size; i++)
{
printf("%d\n", ret->array[i]);
}
del_soft_array(ret);
return 0;
}
运行结果:
union和struct的区别
1、struct中的每个域在内存中都独立分配空间
2、union只分配最大域的空间,所有域共享这个空间
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的使用受系统大小端的影响
大端模式:低位字节放在高地址,高位字节放在低地址
小端模式:高位字节放在高地址,地位字节放在低地址
union C
{
int i;
char c;
};
union C c;
c.i = 1;
printf("%d\n", c.c);//小端模式下,为1.(取一个字节低地址);
三、enum和typedef分析
1、enum是一种自定义类型,是真正意义的常量,等价于命名的常数,是字面常量。
2、enum默认常量在前一个值得基础上依次加1
3、enum类型的变量只能取定义时的离散值,不能是浮点类型。可以显示定义,或默认从0依次累加
#include <stdio.h>
#include <malloc.h>
enum color
{
GREEN,
RED = 5,
BLUE
};
int main()
{
printf("%d\n", GREEN);
printf("%d\n", RED);
printf("%d\n", BLUE);
return 0;
}
运行结果
0
5
6
枚举类型和#define的区别
4、#define宏常量只是简单的进行值替换,枚举常量是真正意义上的常量
5、#define宏常量无法被调试,枚举常量可以。因为define定义的常量会在编译前进行预处理,即进行值替换,所以不可以进行调试
6、#define宏常量无类型信息,枚举常量是一种特定类型的常量
typedef的意义
7、typedef用于给一个已经存在的数据类型重命名
8、typedef并没有产生新的类型
9、typedef重定义的类型不能进行unsigned和signed扩展
typedef和#define的区别
10、typedef是给已有类型取别名
11、#define为简单的字符串替换,无别名的概念
typedef char * PCHAR;
PCHAR p1, p2;
#define PCHAR char *
PCHAR p3, p4
上面的定义中p1、p2、p3都是char *类型的,但p4只是char类型。