一、C语言的本质
C语言的本质是 操作内存。
内存分配的最小单位是 字节byte。
二、内存分配的方式
1.定义变量时:由操作系统负责根据变量的类型,在栈区分配对应大小的空间。
存储类型 数据类型 变量名;
2.由程序员malloc函数在堆区分配空间。
三、C语言中变量的数据类型
基本类型
字符类型 char %c 1字节
短整型 short %d 2字节
整型 int %d 4字节
长整型 long %ld (32位系统)4字节 (64位系统)8字节
长长整型 long long %lld 8字节
单精度浮点型 float %f 4字节
双精度浮点型 double %lf 8字节
多精度浮点型 long double %Lf (32位系统)12字节 (64位系统)16字节
枚举类型 enum
构造类型
数组 int s1[5]; char s2[10];
结构体 struct
共用体(联合体) union
指针类型
大小:32位系统固定4字节 64位系统固定8字节
指针类型的作用:操作空间大小不同,决定了保存的地址开始 ,一共能操作几个字节,
能操作指向类型大小个字节
char *p1 = "hello world";//p1++,操作char类型个字节,操作1个字节
int *p2;
void *p3;
char **pp1 = &p1;//pp1++,操作char*类型个字节,操作8个字节
空类型
void
四、存储类型
4.1 const
const用来修饰变量时,表示修饰的是一个只读变量,不能通过变量名来修改变量的值。
const int a = 10;
printf("a = %d\n",a); √
// a = 20 ; × 不允许通过被const修饰的变量名,修改变量的值
const 修饰指针时,要注意下面的用法,------常见的笔试面试题
const int *p;
int const *p;
int * const p;
const int * const p;
区分时要看 const 和 * 的相对位置关系:
1、如果 const 在 * 的左边,表示修饰的是 *p
不能通过指针修改指针指向空间的内容,但可以修改指针的指向
2、如果 const 在 * 的右边,表示修饰的是 p
指针的指向不能修改,但允许通过指针修改指针指向空间的内容
3、如果 * 的左右都有 const ,表示都不能修改.
#include <stdio.h>
int main(int argc, const char *argv[])
{
#if 0 //const int *p
int a = 10;
int b = 20;
const int *p = &a;
//const 在 * 左边,修饰的是*p
//表示不可以通过*p修改p 指向空间的内容
//但可以修改p的指向
p = &b; // √
a = 100;// 变量自己修改自己的值是OK的,a没被const修饰
#endif
//int const *p; //和上面用法一样
#if 0 //int * const p
int a = 10;
int b = 20;
int * const p = &a;
//const 在 * 右边,const 修饰的是p
//表示可以通过*p修改p指向空间的内容
//但是不可以修改p的指向
*p = 100;
#endif
#if 1
//const int * const p;
int a = 10;
int b = 20;
const int * const p = &a;
// * 两边都有const修饰,
//不可以通过*p 改变p 指向空间的内容
//也不可以改变 p 的指向
#endif
return 0;
}
4.2 static
static 关键字有两个作用:
1、延长局部变量的生命周期,从最近的 {} 结束 延长至 整个程序结束。
2、限制作用域:static修饰的变量1或者函数 只能在当前文件中使用。
#include <stdio.h>
int y = 10;//全局变量
void func1(){
int x = 10;//每次调用函数,都会重新初始化x为10
x++;
printf("x = %d\n",x);
}
void func2(){
//static 修饰的局部变量 存储在data段和bss段
//并且在main函数执行之前完成初始化 不会每次调用函数都初始化
static int x = 10;//并不会每次都赋值10
x++;
printf("x = %d\n",x);
}
int main(int argc, const char *argv[])
{
func1();//11
func1();//11
func1();//11
puts("--------------------------");
func2();//11
func2();//12
func2();//13
return 0;
}
结果:
x = 11
x = 11
x = 11
--------------------------
x = 11
x = 12
x = 13
- static修饰的局部变量,存储在 data 段和 bss 段;
并且在main函数执行之前完成初始化,不会每次调用函数都初始化。
- static修饰的局部变量也是局部变量 ,和 全局变量的作用域 是不一样的。
4.3 extern
声明一个函数或者变量是在其他.c文件中定义的
如果一个 1.c 中想使用 2.c 中定义的变量或者函数
需要在 1.c 中使用关键字 extern 来声明
4.4 register
register关键字修饰的是一个寄存器类型的变量,被执行的效率会更高
cpu取数据的优先级( 寄存器 --> cache高速缓存 --> 内存 )
但是cpu寄存器的个数是有限的 有37的,有40的。。。
所以将所有的变量都修饰成寄存器变量是不现实的。
------实际应用层开发基本用不到---------
注意register修饰的变量不能取地址。( register修饰寄存器类型的变量,而地址是内存的概念 )
register int a = 10;
int *p = &a;// ×错的
4.5 volatile
防止编译器优化的
要求cpu每次取数据,都从内存上面取
volatile使用场景:
1、多线程访问同一个变量
2、中断状态下的寄存器时
4.6 auto
声明一个变量是 自动类型的变量
局部变量定义时如果不加存储类型 默认就是auto类型
非自动类型的变量:
-
- 全局变量
- static 修饰的局部变量
五、多文件编程
大型项目在开发时,不会将代码都写在一个 .c 文件中
而是根据功能不同,将代码拆分成多个 .c 和 .h 文件
.c 是源文件 存放函数的定义
.h 是头文件 存放函数的声明以及一些类型的定义
主函数中一般只负责函数的调用
gcc 编译时,后面要写所有用到的 .c 文件 (使用空格分割) 注意:.h 不可以给 gcc
六、指针复习
6.1 一级指针
int a = 10;
int b = 20;
int *p = &a;
p = &b;
const char *q = "hello";
p++; //p的指向向后移4字节 一个int大小
q++; //q的指向向后移1字节 一个char类型大小
6.2 二级指针
int a = 10;//变量
int *p = &a;//一级指针保存变量的地址
int **q = &p;//二级指针 保存一级指针的地址
64位系统中:
p++; p的指向向后移4字节
(*p)++; *p相当于a ,a++
*p++; 先算p++,++是后置++,本式中并未改变p的值
故*取的值依然是a的地址,相当于先*p 再p++
q++; q的指向向后移8个字节,(移动指向类型个字节)
(*q)++; 相当于p++
*q++; 先算q++,++是后置++,本式中并未改变q的值
故*取的值依然是p的地址,相当于先*q 再q++
(**q)++;**q相当于变量a,(**q)++,就相当于a++
6.3 指针和一维数组
int s[5]; 定义一个数组
int *p = s; 指针p保存数组s的首地址
s[i] <==> *(s+i) <==> p[i] <==> *(p+i)
已知条件:
char *p = "hello world"; hello world存在了.ro区 不允许被修改
char a[] = "hello world"; 此处的hello world 放在了栈区,可以被修改a[1]='E' a是数组首地址,不允许被赋值
char *q;
char b[32];
//判断:表达式对错 对了说明含义 错了说明错在哪
p++; //正确的 p的指向向后移1字节 指向e
*p++; //正确的 p++(p仍然指向h)再* 此表达式结果为h
(*p)++; //错误的 p指向的是(*p的值为h,h是字符常量)字符串常量 字符串常量不能被修改
*p = 'H'; //错误的 p指向的是字符串常量 字符串常量不能被修改
p = "hqyj"; //正确的 指针p指向的修改
a++; //错误的 a是数组名 是常量 不能修改
*a++; //错误的 a是数组名 是常量 不能修改
(*a)++; //正确的 a[0]++; //*a 相当于 a[0]
*a = 'H'; //正确的 a[0] = 'H';
a = "hqyj"; //错误的 a是数组名 是常量 不能修改
*q = 'H'; //野指针 结果不可预知
q++; //正确的 指针的指向后移1字节 从一个野指针变成另一个野指针
q = p; //正确的 指针变量的相互赋值
*q = 'H'; //错误的 q指向的是字符串常量 字符串常量不能被修改
b = a; //错误的 b是数组名 是常量 不能修改 可以 strcpy(b, a);
6.4 数组指针
本质是一个指针,指向二维数组,也叫行指针。
int s[3][4];
int (*p)[4] = s; 定义一个数组指针p指向二维数组s
此处的4 表示一行的跨度(按行操作时 一次偏移4个元素)
s[i][j] <==> *(s[i]+j) <==> *(*(s + i) + j) <=
=> p[i][j] <==> *(p[i] + j) <==> *(*(p + i) + j)
6.5 指针数组
本质是一个数组,数组的每个元素都是一个指针类型。
char *s[4]; 定义了一个指针数组 数组名叫s
数组中有4个元素,每个元素都是一个char *类型的指针
取出数组中任意一个元素操作时,都和操作普通的char *类型指针一样
s[0] = "hello"; s[0]指针指向"hello"字符串的首地址
char value = 'M';
s[1] = &value; s[1]指向变量value的首地址
...
6.6 指针函数
本质是一个函数,返回值是一个指针类型
int *function()
{
}
注意事项:
不能返回局部变量的地址,因为局部变量再函数结束时就被操作系统回收了
可以返回 全局变量的地址、static修饰的局部变量的地址、malloc再堆区分配的空间的地址
6.7 函数指针
本质是一个指针,指向一个函数。
int *function(int *p,int a)
{
//xxx
}
定义一个函数指针p 指向函数function
int *(*p)(int *,int) = function;
后面通过 p 和 function 调用函数就是一样的
6.8 函数指针数组
本质是一个数组,数组中每个元素都是一个函数指针。
void my_add(int x,int y)
{
printf("%d\n",a+b);
}
void my_sub(int x,int y)
{
printf("%d\n",x-y);
}
定义函数指针数组
数组中有两个元素 每个元素都是一个能指向返回值位void
形参列表为(int,int)的函数指针
void (*s[2])(int,int);
s[0]=my_add;
s[1]=my_sub;
s[0](10,20) <==> my_add(10,20);
s[1](20,30) <==> my_sub(20,30);
#include <stdio.h>
int main(int argc, const char *argv[])
{
char *c[] = {"ENTER", "NEW", "POINT", "FIRST"};
char **cp[] = {c+3, c+2, c+1, c};
char ***cpp = cp;
printf("%s\n", **++cpp); // POINT
printf("%s\n", *--*++cpp+3); // ER
printf("%s\n", *cpp[-2]+3); // ST
printf("%s\n", cpp[-1][-1]+1); // EW
return 0;
}