1. 数组指针与指针数组
int *p[5];
int (*p)[5]; //p先和*结合,说明了p是一个指针变量,指向一个大小为5的数组
int (*p)[5]为一个数组指针
int *p[5]则是一个大小为5且存放整型指针的指针数组
- 指针运算
指针指向的是一个地址,因此数组指针也同样可以进行相关运算;例如指针的加减可以实现指针指向数组上一个或者下一个元素的功能
定义指针变量的时候需要定义类型,如果指针p指向了一个数组中的一个元素,那么p+1并不是将地址加上1,而是系统判定类型之后加上一个数组元素所占用的字节数
(即为p+1*d)。
举例说明:
int main()
{
int a[4] = {
2, 0, 1, 9 };
printf("%p, %p\n", a, &a);
printf("%p, %p\n", a + 1, &a + 1);
return 0;
}
结果:0x7fffffffe3e0, 0x7fffffffe3e0
0x7fffffffe3e4, 0x7fffffffe3f0
int main(int argc, char *argv[])
{
int nums[3][3] = {
1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("%d\n", nums[1][-2] );
printf("%d\n", (-1)[nums][5] );
printf("%d\n", -1[nums][5] );
}
/*结果详解:nums[1][-2]=nums[0][1]
(-1)[nums][5]=nums[-1][5]=nums[0][2]
-1[nums][5]=-nums[1][5]=nums[2][2]
*/
相关地址的表示方式,便于理解:
表示 | 含义 |
---|---|
a | 二维数组名,指向a[0] |
a[0], *(a+0), *a | 0行0列元素地址 |
a+1, &a[1] | 1行首地址 |
a[1]+2, *(a+1)+2, &a[1][2] | a[1][2]的地址 |
*(a[1]+2), ((a+1)+2), a[1][2] | a[1][2]的值 |
2. static关键字
static int a = 2018;
static void func(void)
{
static int b;
printf("a = %d,b = %d\n",a++,b++);
}
int main()
{
func();
func();
func();
return 0;
}
结果为
a = 2018,b = 0
a = 2019,b = 1
a = 2020,b = 2
第一个作用:修饰变量(分为修饰全局变量和修饰局部变量两种情况)
1.修饰全局变量:全局变量的值存放在栈上
,其存储类型为静态存储类型
。
全局变量的作用域为从定义全局变量起始处到文件结尾处
没有加static关键字的全局变量,除了可以在其定义的文件之中被引用外,其他文件也可以通过使用extern声明来引用它。
static关键字的全局变量,其他文件即使使用extern声明也无法引用它
2.修饰局部变量:若局部变量前没有加static
关键字,则其值存放在堆上
,存储类型为动态存储类型
,作用域为从变量定义起始处到函数结束处,可以被初始化任意次,若未指定初始化值,则默认初始化值是一个任意数。
若局部变量前加了static关键字
,则其值存放在栈上
,存储类型为静态存储类型
,作用域为从变量定义起始处到文件结尾处,只能被初始化一次,若未指定初始化值,则默认初始化值为0。
3. C语言程序从源代码到可执行文件过程
1.预编译:主处理源代码文件中的以"#"开头的预编译指令,生成.i 文件
2.编译:把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
3.汇编:将汇编代码转换成机器可执行的机器代码,生成.o文件
4.链接:将有关的目标文件彼此相连接
4. const关键字
它是定义只读变量的关键字,或者说 const 是定义常变量的关键字。
说 const 定义的是变量,但又相当于常量;说它定义的是常量,但又有变量的属性,所以叫常变量。用 const 定义常变量的方法很简单,就在通常定义变量时前面加 const 即可
int main()
{
char y[] = "xiyoulinuxgroup",x[] = "2018"
char *const p1 = y;
const char * p2 = y;
return 0;
}
const后面紧跟的是不能改变
p1(地址)不可变,p1指向的对象(值)可变
p2(地址)可变,p2指向的对象(值)不可变
const关键字详细用法
5. define用法
当宏参数是另一个宏的时候,需要注意的是凡宏定义里有用’#’或’##’的地方宏参数是不会再展开.
即, 只有当前宏生效, 参数里的宏不会生效 !
宏定义参数连接符 ##:##主要用于将宏定义中的两个token链接起来,这里的token可以是宏的变量,也可以是任意参数或者标记。
(嵌套宏中,有##时,先展开函数,再展开参数)
宏定义符号#: #能是将其后面的宏参数进行字符串化操作,简单说就是在对它所引用的宏变量 通过替换后在其左右各加上一个双引号
(嵌套宏中,有#时,不展开参数)
宏定义符号#@:将标记转换为相应的单个字符,注意:仅对单一标记转换有效
#define YEAR 2018
#define LEVELONE(x) "xiyoulinux"#x"\n"
#define LEVELTWO(x) LEVELONE(x)
#define MULTIPLY(x,y) x*y
int main(int argc, char *argv[])
{
int x = MULTIPLY(1+2,3);
printf("%d\n",x); // 7 = 1+2*3
printf(LEVELONE(YEAR)); // xiyoulinuxYEAR
printf(LEVETWO(YEAR)); // xiyoulinux2018
}
define中#和##的用法详解
注意:宏定义不是语句,是预处理指令,故结尾不加分号。
6. 结构体字节对齐
计算结构体大小需要遵循的规则
- 第一个成员在与结构体变量偏移量为0的地址处
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
- 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 如果嵌套了结构体的情况,被嵌套的结构体对齐到其自身对齐数的整数倍处(结构体的对齐数就是其内部成员中最大的对齐数),此时结构体的整体大小就是所有最大对齐数(含被嵌套结构体的对齐数)的整数倍。
struct icd{
int a;
char b;
double c;
};
struct cdi{
char a;
double b;
int c;
};
int main(int argc, char *argv[])
{
printf("%zu %zu\n", sizeof(struct icd), sizeof(struct cdi));
}
偏移量:指的是结构体变量中成员的地址和结构体变量地址的差
结构体大小等于最后一个成员的偏移量加上最后一个成员的大小
。
显然,结构体icd中第一个成员的地址就是结构体变量的首地址。因此,第一个成员a的偏移量为0(大小为4)。第二个成员b的偏移量(4+0)是第一个成员的偏移量加上第一个成员的大小,b的大小为(4+1)。第三个成员c的偏移量(5),大小为(5->8 补齐 ,8+8=16)
7. main函数
在C99标准中定义main函数两种正确的写法
int main(void);
int main(int argc, char* argv[]);
参数
- argc : main函数参数个数,当参数为void的时,argc=1,默认参数为可执行文件名
- argv : 指针数组,分别指向个参数字符串首地址,其中argv[0]指向默认参数
main函数返回值
main函数的返回值用于说明程序的退出状态。如果返回0,则代表程序正常退出。返回其它数字的含义则由系统决定。通常,返回非零代表程序异常退出。
8. 大小端
大端存储:就是把一个数的低位字节序的内容存放到高地址处,高位字节序的内容存放在低地址处。
小端存储:就是把一个数的低位字节序的内容存放到低地址处,高位字节序的内容存放在高地址处。
puts((char*)(int const[])
{
0X6F796958,0X6E694C75,0X72477875,
0X3270756F,0X313230,0X00000A
});
结果:XiyouLinuxGroup2021
-
大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
-
小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
9. 逻辑运算
& | I | ^ | << | >> | ~ |
---|---|---|---|---|---|
按位与 | 按位与 | 异或 | 按位左移 | 按位右移 | 按位取反 |
int main(int argc, char * argv[])
{
char ch = 'A';
int i = 65;
unsigned int f = 33554433;
*(int *)&f >>= 24;
*(int *)&f = *(int *)&f + '?';
printf("ch = %c i = %c f = %c\n", ch, i, *(int *)&f);
return 0;
}
结果:ch = A i = A f = A
左移与右移比较类似,是将目标二进制数字向左/右移动相应的位数。
左移补0:1111 1111 <<1 == 1111 1110,换算十进制的话是原来数值的2倍。
右移看情况:负数补1,正数补0.需要看符号位。同样,换算为十进制数值变为原来的1/2.
总结:左乘右除。