(1)指针
指针:指针就是地址,表示每个字节在内存中的标号
指针变量:就是一个容器,用来存储指针
(加快访问速度,不会发生数据丢失)
一、指针的定义格式
格式:存储类型 数据类型 *指针变量名
int *p; //p的类型是int*,int表示指针指向地址对应值的类型,指针变量加减运算的偏移量;
char*p1;
void *p2; //通用类型指针,使用时需要类型强转,多用来传参和返回
二、指针的字节大小
64位操作系统---------------------8字节
32位操作系统---------------------4字节
三、指针的定义和初始化
1、指针的类型和变量的类型保持一致
int a = 100;
int *p = &a;
char b = 'A';
char *q;
q = &b;
2、指针的类型和变量的类型不一致
int a = 0x12345678;
char *p = (char *)&a;
short *q = &a;
3、定义指针未初始化,称为野指针
野指针:非法访问内存空间
缺点: 段错误、计算机混乱、死机
4、当定义指针不清楚指向谁,指向NULL
int *p = NULL;
NULL:表示0号地址,不可使用,只是用来指针指向
int *p = 0;
5、可以使用已经初始化的指针对另一个指针初始化
int a = 100;
int *p = &a;
int *q = p;
6、不可以让指针初始化为值
int a = 100;
int *p = a; //error
int *p = 100; //error
int *p = 0x7f7f8s8ddg; //error
四、解引用和取地址
* 解引用(取地址对应的值)
& 取地址
1、值:a--->*p--->*&a
2、地址:&a--->p--->&*p
3、*和&属于逆运算
五、指针的运算
算数运算:+- ++ --,
关系运算:> >= <
赋值运算:= += -=
1、算数运算
int a = 100; int *p = &a; | ||
运算符 | 例子 | 解释 |
+、- (+号为例) | &a+n | 向高地址偏移n倍的数据类型(int)字节大小 |
*p+n | 先取p指针指向地址对应的值,然后对值+n | |
*(p+n) | 向高地址方向偏移n倍的数据类型字节大小,在该地址的值 | |
p+n | 向高地址方向偏移n倍的数据类型字节大小 | |
++、-- (++为例) | (&a)++\++(&a) | error &a是常量 |
p++ | 后缀运算,先取,后向高地址方向偏移一个数据类型字节大小 | |
++p | 前缀运算,先向高地址偏移一个数据类型字节大小,后取 | |
*++p | 先++p,向高地址偏移一个数据类型字节大小,再取值 | |
*p++ | 先p++,++和p结合,++是后缀,先取值,在对p自增,向高地址方向偏移一个数据类型字节大小 | |
++*p | 先*p,先取值,对值自增 | |
*(p++) | 先p++,++和p结合,++是后缀,先取值,在对p自增,向高地址方向偏移一个数据类型字节大小 | |
*(++p) | 先++p,前缀运算,向高地址方向偏移一个数据类型字节大小,在取值 | |
(*p)++ | 先*p,先取值,在后缀自增,先运算,后自增 |
2、赋值运算
= | int a=100;int *p=&a; | 指针变量p指向变量a的地址,注意需要保证=左右两边类型一致 |
+= | p+=1--->p=p+1 | p向高地址方向偏移一个数据类型字节大小 |
-= | p-=1--->p=p-1 | p向低地址方向偏移一个数据类型字节大小 |
3、关系运算
> | p>q | 条件成立则返回1,否则返回0 |
>= | p>=q | 条件成立则返回1,否则返回0 |
< | p<q | 条件成立则返回1,否则返回0 |
<= | p<=q | 条件成立则返回1,否则返回0 |
== | p==q | 条件成立则返回1,否则返回0 |
!= | p!=q | 条件成立则返回1,否则返回0 |
练习:在主函数中定义字符串并初始化,实现字符串逆置
char str[]="hello" str str+1 "h e l l o \0" 0x10 0x11 0x12 0x13 0x14 0x15 start end 0 1 2 3 4 i j char *start=str,*end=str+strlen(str)-1; while(start<end) { t=*start;*strat=end.... start++;end--; } while(i<j) { t=str[i];str[i]=str[j];str[j]=t; i++;j--; }
六、一维数组和指针
格式: 数据类型 *指针变量=数组名
int arr[] = {1,2,3}
int *p = arr;
地址:&arr[i] <==> &arr[0]+i <==> arr+i <==> &p[i] <==> &p[0]+i <==> p+i <==> p++
值:arr[i] <==> *(&arr[0]+i) <==> *(arr+i) <==> p[i] <==> *(&p[0]+i) <==> *(p+i) <==> *p++
冒泡排序
// 冒泡排序 // int type 0 倒序 ,1 正序 void sort_array(int *p, int n, int type){ int i = 0; int j = 0; int temp = 0; while(i < n-1){ while(j < n-i-1){ if(type == 1 ? (*(p+j)>*(p+j+1)) : (*(p+j)<*(p+j+1))){ temp = *(p+j); *(p+j)=*(p+j+1); *(p+j+1)=temp; } j++; } j = 0; i++; } i = 0; while(i<n){ printf("%d ",*(p+i)); i++; } printf("\n"); }
七、值传递和地址传递
1、值传递
值传递:传递的是值(看实参)
形参的改变不影响实参
形参和实参分别占有存储空间
单项传递
2、地址传递
地址传递:传递的是地址
形参的改变影响实参
形参和实参共享存储空间
单项传递
八、多级指针
格式:存储类型 数据类型 **变量名称(*个数任意)
一级指针存储变量的地址
二级指针存储一级指针的地址
三级指针存储二级指针的地址
。。。
int a;
int *p=&a;
int **q=&p;
int ***m=&q;
九、通用类型指针
格式:void*指针变量名
可以指向任意类型的地址,但是使用时需要类型强转,一般多用于传参和返回
int main(int argc, const char *argv[]) { int a=100; void *p=&a; printf("*p=%d\n",*(int*)p); return 0; }
十、二维数组和指针
格式:int arr[2][3] = {11,22,33,44,55,66}
int (*p)[3]=arr; // 数组指针 p->arr
p->arr
&arr[0][0] -->&p[0][0]
arr[0] -->p[0]
&arr[0] -->&p[0]
arr -->p
&arr 不等价-->&p
十一、数组指针
数组指针:本质上是一个指针,用来指向二维数组变量名,指向一维数组为整体的首址编号
格式: 存储类型 数据类型 (*数组指针变量名)[常量表达式]
int arr[3][5]
int (*p)[5] = arr;
p的类型是:int(*)[5],大小是指针的字节大小
十二、指针数组
指针数组:本质上是一个数组,存储多个类型相同的指针
格式: 存储类型 数据类型 *指针数组变量名[常量表达式]
int *p[5]; //定义5个int*类型的指针
p的类型是 int *[5],占5*8字节大小
解析
1.常量表达式: 指针的个数,数组长度,初始化时可以省略不写,默认是实际元素的个数
int a = 1, b = 2, c = 3, d = 4; // ----->int arr[4] int *p1 = &a; int *p2 = &b; int *p3 = &c; int *p4 = &d; int *p[4] = {&a, &b, &c, &d};
十三、字符数组和指针
格式: char str[10]
char *p = str;
1.通过指针操作字符串 int main(int argc, const char *argv[]) { char str[10]=""; char *p=str;//p-->str gets(p); puts(p); return 0; } 2.指针指向字符数组变量 char str[]="hello"; char *p=str;//p指针指向字符数组str的首地址 i.可以通过指针修改元素 eg: *(p+1)='E' ii.当不同的数组存储相同的字符串时,地址不同 3.指针指向字符串常量 char *q="hello";//指针q指向字符串常量的首地址,就是字符'h的地址' i.不允许通过指针修改元素 eg: *(q+1)='E' 段错误 ii.不同的指针指向同一个字符串时,地址相同
十四、字符指针数组
字符指针数组:本质上是一个数组,存储多个字符指针
格式: char *指针数组变量名[常量表达式]
1.字符指针数组指向字符数组变量的地址 char a[]="123"; char b[]="abc"; char c[]="ABC"; char *p1=a; char *p2=b; char *p3=c; char *p[3]={a,b,c}; 0 1 2 p[0]-->a puts(a) for(i=0;i<3;i++) { puts(p[i]); } 2.字符指针数组存储多个字符串常量的地址 char *p[3]={"123","abc","ABC"}; for(i=0;i<3;i++) { puts(p[i]); }
十五、指针函数
指针函数定义:本质上是一个函数,返回了一个地址,注意不允许返回局部变量的地址
格式:
数据类型* 函数名(参数列表) {
函数体;
return 地址;
}
十六、函数指针
函数指针:本质上是一个指针,用来指向函数的的首地址,函数名表示函数的首地址
格式: 存储类型 数据类型 (*函数指针变量名)(参数列表)
void sum1(); void (*p)() = sum1 p的类型是void(*)() void sum2(int a, float b) void (*p)(int, float) = sum2 p的类型是void(*)(int float) float sum3() float (*p)() = sum3 float sum4(int a, float b) float (*p)(int, float)= sum4 char* strcpy(char *dest, const char *src); char* (*p)(char*,const char*)=strcpy
函数指针主要使用在回调函数:函数名作实参,函数指针作形参接收函数的首地址
十七、函数指针数组
函数指针数组:本质上是一个数组,存储多个类型相同的函数指针
格式: 存储类型 数据类型 (*函数指针数组变量名[常量表达式])(参数列表)
float Sum(int a,float b) float (*p1)(int,float)=Sum float Sub(int a,float b) float (*p2)(int,float)=Sub float Mul(int a,float b) float (*p3)(int,float)=Mul float Div(int a,float b) float (*p4)(int,float)=Div float (*p)(int, float) = {Sum, Sub, Mul, Div} Sum(a,b) (p[i])(a, b) p占4*8,p的类型 float (*[4])(int,float)
(2)存储类型
一、auto
自动类型变量
1.局部变量省略存储类型默认是auto
2.auto不允许修饰全局变量
3.auto类型的变量内存默认在栈区
4.auto修饰局部变量,会初始化为0
二、static
static作用:静态变量,延迟局部变量的生命周期
1、static修饰局部变量:延迟局部变量的生命周期至整个文件
2、static修饰函数:把函数的生命周期延长至本文件结束,不可以跨文件使用
3、static修饰指针:不允许使用static修饰的指针指向auto类型变量的地址
计算机先给static类型的变量分配内存,后分配auto
三、volatile
防止内存优化,保持内存的可见性
四、const
1.const作用:修饰的值不可改变
2.const修饰的全局变量内存在静态区的只读段
3.const修饰的局部变量内存在栈区
4.const和指针结合
const char *p *在const的右边,const修饰的值,值不变,地址可变
char const *p *在const的右边,const修饰的值,值不变,地址可变
char * const p *在const的左边,const修饰的地址,地址不变,值可变
const char * const p 第一个const修饰是值,第二个const修饰的值地址,值和地址均不变
char const * const p 第一个const修饰是值,第二个const修饰的值地址,值和地址均不变
五、extern
引用外部变量
1、全局变量和函数省略存储类型默认是extern
2、extern类型的变量默认内存在静态区
3、不可以引用static修饰的变量
六、register
寄存器变量
内存---->高速缓存---->寄存器
1、寄存器类型的变量不可以取地址操作
(3)宏
宏:宏是常量,只做替换,不做计算,不做正确性的检查
1.宏:不属于C语句
2.发生在预处理阶段
3.一旦定义不可以修改
一、宏
格式:#define 宏名 宏体
解析:
1.#define: 表示是一个预处理
2.宏名:满足命名规范,建议大写
3.宏体:默认是字符串
4.宏默认是全局变量
二、宏自定义函数
i.使用({})
格式: #define 宏函数名(参数) ({表达式1;表达式2;....表达式n})
1.宏函数名:建议大写
2.参数: 只写名字,没有数据类型
3.({}) 当只有一个表达式时,可以省略不写,默认返回最后一个表示的结果
ii.do..while(0)
格式: #define 宏函数名(参数) do{表达式1;表达式2;....表达式n}while(0)
1.宏函数名:建议大写
2.参数: 只写名字,没有数据类型
3.{}do..while(0)当只有一个表达式时,可以省略不写,默认是一个无返回值
三、宏系统函数
==============判断宏是否为真====
#if 宏 #if 0
C语句;
#endif
执行过程:判断宏为真,则执行C语句,为假跳过,多用于注释
#if 宏
C语句1
#else
C语句2
#endif
执行过程:如果宏为真,则执行C语句1,否则执行C语句2
#if 宏1
C语句1
#elif 宏2
C语句2
.....
#else
C语句n
#endif
执行过程:如果宏1为真,则执行C语句1,结束,
如果宏1为假,则判断宏2,如果宏2为真,则执行C语句2,结束
重复以上过程,如果条件都为假,则执行C语句n
========================判断宏已经定义=====
#ifdef 宏
C语句
#endif
过程:判断宏已经定义,则执行C语句,未定义则跳过
=======================判断宏未定义=======
#ifndef 宏
C语句
#endif
过程:判断宏未定义则执行C语句,否则跳过
======================判断多个宏定义=====
#if defined(宏1) && defined(宏2) //可以判断多个宏
C语句
#endif
=======================判断多个宏未定义=====
#if !defined(宏1) && !defined(宏2) //判断多个宏未定义
C语句
#endif
======================取消宏====
#undef 宏
=======================# 转换字符串
#define M(x) #x
#define N(x) x
int main(int argc, const char *arg
{
// printf("%s\n",M(hello)); //"hello"
printf("%s\n",N(hello)); //hello
return 0;
}
========================## 拼接
#define A(x,y) x##y
int main(int argc, const char *argv[])
{
int num2=100;
printf("%d\n",A(num,2)); //num2
return 0;
}
四、多文件编译
头文件:预处理命令:宏、头文件、全局变量、函数声明
主函数文件:主函数
自定义文件:自定义函数