指针
普通的变量传递到函数中对其进行赋值操作并不会影响原来实参的值
数组作为参数传递到函数中如果对数组中的内容进行修改会影响实参数组元素的值
C语言中参数的传递其实是值复制传递
在函数调用时,实参把值赋值给形参 实参和形参是不同的两个变量
修改形参并不会影响实参的值
变量的地址: 内存地址 (变量存储在内存中的位置 编号 )
可以用%p 显示一个变量的地址 十六进制的数字
任意类型变量取地址运算符 & 内存地址
&取址运算符的操作数必须是左值
内存地址是一个十六进制的数值,可以用变量来保存变量的地址
内存地址即指针
定义指针变量来保存内存地址
一个程序(进程)有4G的虚拟内存 4G用数值[0,0xFFFFFFFF]
4G的内存 编号从0开始,到0xFFFFFFFF
4G = 4*1024MB = 4*2^10 * 2^10 KB = 4*2^20 * 2^10B = 4*2^30Byte
= 2^32Byte 字节
4G内存有2^32个字节 一个字节一个内存编号 编号[0,2^32 -1]
"虚拟内存 物理内存"
指针变量:
即保存内存地址的变量
一块内存用于保存什么数据 决定了该内存的大小
char 1byte
int 4byte
double 8byte
指针变量的定义:
数据类型 * 指针变量;
数据类型: 指明了内存地址的宽度(内存地址中存储数据的类型)
*: 指明是指针变量
数据类型* 整体 称为 指针变量的类型
注意:
int *p1,*p2;//定义两个指针变量 p1和p2的类型都是int*
int* p1,p2; //p1类型为int* 即指针 p2类型为int 即整型
int *p1; int* p2; int * p3; int*p4;//都没问题 都是int* 类型
指针变量的初始化:
int a = 0;//一般来说 定义变量时会对其初始化 “零”
int *p = NULL;
空指针 NULL 悬空指针 本质上是 编号为0的地址
int a = 10;
int *p = &a;//有确切的值初始化
切记:当一个指针定义之后 如果不确定存储什么数据,一定要初始化为NULL
指针变量也是变量,存储的值也可以发生变化
取值运算符*:
* 取值运算符 取内存地址中存储数据的数值
*操作数 操作数必须是内存地址 内存地址就是指针
int a = 10;
*&a === a 因为&a是取a变量的地址 *(&a) 取地址中的数值
写一个函数交换两个整数变量的值
int a = 10,b = 20;
野指针:
如果一个指针变量没有初始化,不能直接对其进行 * 运算
int *p;// 野指针 p是垃圾值 即一块不确定的内存
根本无法确定内存是否可以使用 或者有权限使用
段错误(核心已转储)
野指针: 指针的值不确定 无法确定一个指针是否是野指针
空指针(悬空指针): NULL
建议指针初始化为NULL
在判断一个指针是否可以进行*运算时:
if(p != NULL){
*p;//运算
}
[]运算符:
数组名[下标] 等价 下标[数组名]
arr[i] <> *(arr+i) <> i[arr]
&arr[0] 首元素地址
<===> &(arr[0]) <===> &(*(arr+0)) <===> &(*arr) <==> arr
任意的 &* 和 *& 相互抵消
数组名即 数组首元素地址 第一个元素的地址
sizeof(指针) == 4/8
指针即内存大小 内存其实是用一个0-4G编号(数值) 0,2^32
只需要4个字节就可以保存任意一个内存地址 指针变量的字节大小恒为4
不同的操作平台 sizeof(指针) 不一样
linux i386 cpu 32位 sizeof(指针) == 4
linux x64 cpu 64位 sizeof(指针) == 8
数组名作为实参传递时,退化为首元素地址(指针)
所以在形参表中,即使 int arr[10] <> int arr[]>> int *arr
所以这就是为什么数组在传递时必须传递数组长度的原因
void func(int arr[],int len);
void func(int *arr,int len);
指针与一个整数进行算术加减:
指针+1: 指针偏移一个单位内存大小
int * +1 +4byte
double * +1 +8byte
char * +1 +1byte
int arr[5];
&arr +1 +20byte
##指针
指针即内存地址
内存地址: 一个i386程序拥有4G的内存地址 为了方便管理对这4G内存进行编号
[0,0xffffffff] [0,2^32 -1] 4G=22G=22 * 2^10 * 2^10 * 2^10 Byte = 2^32 Byte
一个内存地址只能存储1字节数据 = 8bit
一个整数4个字节,存储一个int类型数据里 需要4个字节的内存空间
取址运算符&:
&变量 取变量所在的内存地址(开始地址)
取值运算符*:
*内存地址 取得内存地址中保存的数值
*内存地址 内存地址得有类型才被允许
内存地址的类型 即 指针类型
定义指针变量:
数据类型 * 指针变量;
int *p1; int* p2; int * p3; int*p4;
int *p1,p2; //p1的类型是int*, p2的类型是int
指针变量初始化:
一个指针变量如果不确定保存什么内存,就会初始化为NULL
NULL 一个特殊的内存地址 编号为0的内存地址 该内存地址不可能取*运算
NULL(0)内存地址不允许进行访问
NULL 空指针(悬空指针) 用于判断一个指针变量是否是可以取*
野指针:
一个指针没有初始化,或者一个指针的指向不明确
野指针非常危险的,编译器无法检查一个指针是否是野指针(数组越界 编译检测不出来)
编程中需要时刻注意的问题:
1.数组越界
2.野指针
指针变量 也是 变量, 能够存储数据 这个数据是一个内存地址编号(0-4G)
只需要4个字节就可以保存任意一个内存地址
i386 cpu sizeof(指针变量) == 4byte
x64 cpu sizeof(指针变量) == 8byte
指针变量存储一个内存地址,指针指向一个内存地址(指向即保存)
指针变量的值 %p 输出 十六进制的地址编号
*指针变量 得到 指向所指向内存地址中 保存的数据
char c = 'A'; int a = 10; dobule d = 3.14;
char *p1 = &c; //&c是char数据的地址 char *
int *p2 = &a; //&a是int数据的地址 int *
double *p3 = &d; //&d是double数据的地址 double *
内存地址不就是一个数值编号,为什么需要有类型?
*内存地址 取内存地址的数据
char * *内存地址 取一个字节数据
int * *内存地址 取四个字节数据
double * *内存地址 取八个字节数据
指针变量的操作:
int a = 10, b = 20;
int *p = &a;//指针p指向a变量 p存储a变量的地址
p本身的值是一个内存地址
*p取到的是a变量的值
*p = 111;//改变的是a的值
p = &b; //改变的是指针变量p本身的值 p指向b变量 p存储b变量的地址
*p取到的是b变量的值
*p = 9527;//改变的是b的值
指针与数组:
数组名 即 数组首元素地址
[]运算符:
arr[index] <====> *(arr+index)
&arr[0] == &(*(arr+0)) == &(*arr) == &*arr == arr
&arr 值会等于 arr 和 &arr[0] 但是意义完全不一样
arr和&arr[0]都是首元素的地址 &arr是整个数组的地址
指针的算术运算:
指针变量 + 1
指针变量+1 偏移了一个单位长度的内存地址
char * +1 : 1byte
int * +1 : 4byte
double * +1 : 8byte
int arr[10]; &arr+1 : 40byte
&arr[0] arr+1 : 4byte
数组元素的访问:
int arr[5] = {1314,520,9527,119,110}
int *p = arr;
for(int i=0;i<5;i++){
arr[i]
i[arr]
*(arr+i)
p[i]
i[p]
*(p+i)
}
一维数组在作为参数传递时,退化为首元素地址类型,
所以数组在传递时需要传递数组长度
void func(int arr[]);
void func(int *arr);
void func(int arr[5]);
int arr[5] = {1,2,3,4,5};
func(arr);
void goo(int arr[5]){//arr就是一个指针 5没有意义 数组长度需要另传
printf("%u\n",sizeof(arr));//4
}
void bar(int arr[]){
printf("%u\n",sizeof(arr));//4
}
void foo(int *arr){
printf("%u\n",sizeof(arr));//4
}
void func(){
int arr[5] = {1,2,3,4,5};
printf("%u\n",sizeof(arr));//20
}
指针的应用:
在函数中如果需要修改实参的结果 需要用到 指针
数组传递 退化为 指针
作为函数的返回值类型(需要注意一些问题)
函数返回指针时,不能返回局部变量的地址(函数调用之后,该地址会被回收)
局部变量: 在函数内部定义的变量 + 形参列表
可以返回全局变量的地址 也可以返回堆内存地址
//写个函数 交换两个变量的值
//No 交换形参的值 并不影响实参
void swap1(int a,int b){
int tmp = a;
a = b;
b = tmp;
}
//No 交换的是形参pa和pb的值 两个指针的指向发生交换
void swap2(int *pa,int *pb){
int *pt = pa;
pa = pb;
pb = pt;
}
//Error pt是野指针
void swap3(int *pa,int *pb){
int *pt;
*pt = *pa;
*pa = *pb;
*pb = *pt;
}
//Yes
void swap4(int *pa,int *pb){
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
int a = 10,b = 20;
swap1(a,b);
swap2(&a,&b);
swap3(&a,&b);
swap4(&a,&b);
万能指针:
指针即内存地址 数值编号 用指针变量存储
万能指针 能够存储任意类型的地址
void *p;
char c = 'a'; int a = 10; double d = 3.4;
p = &c;
p = &a;
p = &d;
万能指针变量能够保存一个地址,
但由于失去了该地址的类型,万能指针不能取*运算
如果要对万能指针取*运算,必须对万能指针强制转换为对应类型的指针
*(目标类型*)万能指针
二级指针:
二级指针是一级指针变量的内存地址
int a = 10;
int *p = &a;
int **pp = &p;
pp == &p
*pp == *&p == p == &a
**pp == *p == *&a == a
//不是交换ppa和ppb 而是交换*ppa *ppb
void swap(int **ppa,int **ppb){
int *pt = *ppa;
*ppa = *ppb;
*ppb = pt;
}
int a = 10,b = 20;
int *pa = &a, *pb = &b;
//写一个函数 交换pa和pb的值
swap(&pa,&pb)
##字符串
本质:在C语言中,没有字符串这种数据类型
定义: 在内存中一串连续且以’\0’为结尾标识的字符 称为字符串
连续
‘\0’作为结尾标识 ‘\0’是一个特殊的字符 字符串结尾标识 ascii 0
字符串的表现形式:
1.字面值字符串 字符串字面值
用""引起来的一串字符 末尾默认有’\0’的存在
“Hello world” “中国万岁”
特点:
(1)字面值不能修改 存储在代码区
(2)字面值相同的字符串在内存中只存储一份
(3)连续的字面值字符串自动合并成一个字符串
"Hello "“World” == “Hello World”
2.字符数组
char str[10] = {‘H’,‘e’,‘l’,‘l’,‘o’,’\0’};
//语法允许 没有错误 但不是一个合法的字符串 因为没有内存存储’\0’
char s[5] = {‘H’,‘e’,‘l’,‘l’,‘o’};
字符数组要确保有足够的内存来存储’\0’
特点:
(1)存储在栈区 允许修改
(2)字符数组初始化
char s1[10] = {‘H’,‘e’,‘l’,‘l’,‘o’}; //’\0’可以明写 可以省略
char s2[10] = “Hello”;
char s3[5] = “Hello”;//错误的
s3只能存储5个字节的数据 "Hello"有6个字节
上述两种方式 只能在定义时初始化
一旦定义之后不能再对整体进行赋值 (数组特性)
(3)如果需要对字符串内存进行操作,一般都是用字符数组
3.字符指针 char*
字符指针变量不会保存字符串本身,只是保存字符串开始位置
既可以保存字面值字符串的首地址,也可以保存字符数组的首地址
输出字符串的格式占位符:%s
从某个内存地址开始输出字符,直至遇到'\0'停止输出
从某个内存地址开始到'\0' 看作是字符串
sizeof与字符串strlen:
sizeof是C语言的关键字,用于求变量或者类型数据的字节宽度
strlen是C语言string.h头文件中一个函数,用于计算字符串的字符个数
sizeof会考虑'\0'所占的内存
strlen不包含'\0'
都能接收 char *
sizeof(char *) === 4
strlen(char *) === 不同的字符串结果不同
字符的操作: #include <string.h>
(1)字符串长度 字符个数 不包含'\0'
size_t strlen(const char *s);
size_t <===> unsigned int
const: 表示只读 不能修改
(2)字符串拷贝 复制
把src字符串按字符拷贝到dest中 并且返回dest字符串
char *strcpy(char *dest,const char *src);
从src中拷贝n个字节数据到dest,如果src中没有n个字节数据 把src拷贝就完事了
char *strncpy(char *dest,const char *src,size_t n);
(3)字符串追加 在字符串末尾加上新的字符串
char *strcat(char *dest,const char *src);
char *strncat(char *dest,const char *src,size_t n);
(4)字符串比较
int strcmp(const char *s1,const char *s2);
int strncmp(const char *s1,const char *s2,size_t n);