指针相关以及多文件编程

一、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
  1. static修饰的局部变量,存储在 data 段和 bss 段;

并且在main函数执行之前完成初始化,不会每次调用函数都初始化。

  1. 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类型

非自动类型的变量:

    1. 全局变量
    2. 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值