1.指针和内存单元
指针: 地址。
内存单元: 计算机中内存最小的存储单位。——内存单元。大小一个字节(8位)。 每一个内存单元都有一个唯一的编号(数)。 称这个内存单元的编号为 “地址”。
指针变量:存地址的变量。
int a;
printf("打印a变量的地址:%p\n", &a); // 打印a变量的地址:0000007B5476F5C4 16位*4=64位
2.指针定义和使用:
int a = 10;
int *p = &a; int* p;--- windows; int *p ---Linux int * p ; "*" 是一个类型描述符,描述整型指针
*p = 250; 指针的 解引用( 间接引用)
*p :
将p变量的内容取出,当成地址看待,找到该地址对应的内存空间。
如果做左值(在等号左边): 存数据到空间中。
如果做右值(在等号右边): 取出空间中的内容。
int a = 10;
printf("原来: &a = %p , a = %d\n", &a,a);
int* p = &a;
printf("原来: &p = %p , p = %p\n", &p, p);
*p = 2000; // a = 2000;
printf("变化后: &a = %p , a = %d\n", &a, a);
printf("变化后: &p = %p , p = %p\n", &p, p);
/*
原来: &a = 0000001B68DDF654 , a = 10
原来: &p = 0000001B68DDF678 , p = 0000001B68DDF654
变化后: &a = 0000001B68DDF654 , a = 2000
变化后: &p = 0000001B68DDF678 , p = 0000001B68DDF654
*/
*/
printf("sizeof(int *) = %u\n", sizeof(int *)); // 8
printf("sizeof(float *) = %u\n", sizeof(float *)); // 8
printf("sizeof(double *) = %u\n", sizeof(double *)); // 8
int a, *p, *q, b;
任意“指针”类型大小: 指针的大小与类型 无关。 只与当前使用的平台架构有关。 32位:4字节。 64位: 8字节。
野指针:
1) 没有一个有效的地址空间的指针。
int *p;
*p = 1000;
2)p变量有一个值,但该值不是可访问的内存区域。
int *p = 10;
0-255地址是确定留给操作系统使用的,所以这里是不可用的地址。随便一个数字即使超过255,比如1000,但是你不知道它是否被使用,所以不可以在定义时将值直接赋给指针变量。所以在使用时都是利用另外一个变量进行初始化。
*p = 2000;
【杜绝野指针】
空指针:
int *p = NULL; #define NULL ((void *)0)
*p 时 p所对应的存储空间一定是一个 无效的访问区域。
万能指针/泛型指针(void *):
可以接收任意一种变量地址。但是,在使用【必须】借助“强转”具体化数据类型。
int a = 345;
void* p; // 万能指针
p = &a;
printf("%d\n", *((int *)p)); // 345, (int *)p:把p强转成为int * 指针类型,*[(int *)p]:取p指针的内容
/*--------------------------------------*/
char ch = 'R';
void *p; // 万能指针、泛型指针
p = &ch;
printf("%c\n", *(char *)p);
3.const关键字:
3.1修饰变量:
/*
const int a = 20;
a = 5; // 这一句话会报错,因为a值被确定了
*/
const int a = 20;
int *p = &a;
*p = 650;
printf("%d\n", a);
3.2修饰指针:
const int *p; 可以修改 p ,不可以修改 *p。
int const *p; 同上。
int * const p; 可以修改 *p,不可以修改 p。
const int *const p; 不可以修改 p ,不可以修改 *p。
总结:const 向右修饰,被修饰的部分即为只读(不可修改)。
常用:在函数形参内,用来限制指针所对应的内存空间为只读。
int a = 10;
int b = 30;
//const int* p = &a;
//*p = 500; // 报错
//p = &b;
//int const * p = &a;
//*p = 500; // 报错
//p = &b;
//int * const p = &a;
//*p = 500;
//p = &b; // 报错
const int * const p = &a;
*p = 500; // 报错
p = &b; // 报错
4.指针和数组:
4.1数组名是地址常量】
— 不可以被赋值。 ++ / – / += / -= / %= / /= (带有副作用的运算符,这些变量会使变量改变)
int a[] = {1,2,3};
int b[3];
b = a; // 这句话会报错,因为数组名b是常量
4.2 指针是变量。
可以用数组名给指针赋值。 ++ –
int a[] = {1,2,3};
int b[3];
int *p = b;
p = a; // 这句话是可以的,p是变量
4.3 取数组元素:
int arr[] = {1,3, 5, 7, 8};
int *p = arr;
arr[0] == *(arr+0) == p[0] == *(p+0)
int a[] = { 1,2,3,4,5,6,7,8,9,0 };
int n = sizeof(a) / sizeof(a[0]);
int* p = a;
for (size_t i = 0; i < n; i++)
{
//printf("%d ", a[i]); // a[i]== *(a+i)
//printf("%d ", *(a + i));
//printf("%d", p[i]); // p = a
printf("%d", *(p + i));
}
4.4 指针和数组区别:
1. 指针是变量。数组名为常量。
2. sizeof(指针) ===》 4字节 / 8字节
  sizeof(数组) ===》 数组的实际字节数。
指针++ 操作数组:
// 使用指针++操作数组元素
int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr;
int n = sizeof(arr) / sizeof(arr[0]);
printf("开始时:p = %p\n", p);
for (size_t i = 0; i < n; i++)
{
printf("%d", *p);
p++; // 一次加过一个Int大小
} // p指针指向一块无效的内存区域,p为野指针
printf("\n");
printf("结束时:p = %p\n", p);
/*
开始时:p = 000000872757FB48
1234567890
结束时:p = 000000872757FB70
*/
p的值会随着循环不断变化。打印结束后,p指向一块无效地址空间(野指针)。
4.5 指针加减运算:
数据类型对指针的作用
1)间接引用:决定了从指针存储的地址开始,向后读取的字节数。 (与指针本身存储空间无关。)
2)加减运算: 决定了指针进行 +1/-1 操作向后加过的 字节数。
int a = 0x12345678;
short* p = &a;
printf("p = %p\n", p);
printf("p+1=%p\n", p + 1);
/*
p = 0000005250F7FBD4
p+1= 0000005250F7FBD6
*/
5.指针运算
5.1 指针 * / % : error!!! 因为指针是地址
5.2 指针 ± 整数:
1) 普通指针变量±整数
char *p; 打印 p 、 p+1 偏过 1 字节。
short*p; 打印 p 、 p+1 偏过 2 字节。
int *p; 打印 p 、 p+1 偏过 4 字节。
2)在数组中± 整数
short arr[] = {1, 3, 5, 8};
int *p = arr;
// int *p = &a[5]; // 指向第五个元素的地址
p+3; // 向右(后)偏过 3 个元素
p-2; // 向前(左)偏过 2 个元素
3)&数组名 + 1
加过一个 数组的大小(数组元素个数 x sizeof(数组元素类型))
int a[] = {1,2,3,4,5,6,7,8,9,0};
printf("a = %p\n", a);
printf("&a[0] = %p\n", &a[0]);
printf("a+1 = %p\n", a + 1);
printf("&a = %p\n", &a);
printf("&a+1 = %p\n", &a + 1);
/*
a = 000000616B3DFA78
&a[0] = 000000616B3DFA78
a+1 = 000000616B3DFA7C
&a = 000000616B3DFA78
&a+1 = 000000616B3DFAA0
8+2*16 = 40 字节
*/
5.3 指针 ± 指针:
指针 + 指针: error!!!
指针 - 指针:
1) 普通变量来说, 语法允许。无实际意义。【了解】
2) 数组来说:偏移过的元素个数。
int a[10] = {1,2,3,4,5,6,7,8,9,0};
int* p = &a[3];
printf("p-a = %p\n", p - a); // 3
【指针实现 strlen 函数】:
char str[] = "hello";
char *p = str;
while (*p != '\0')
{
p++;
}
p-str; 即为 数组有效元素的个数。
5.4指针比较运算:> < =
1) 普通变量来说, 语法允许。无实际意义。
2) 数组来说: 地址之间可以进行比较大小。
可以得到,元素存储的先后顺序。
3)空指针
int *p;
p = NULL; // 这两行等价于: int *p = NULL;
if (p != NULL)
printf(" p is not NULL");
else
printf(" p is NULL");
5.指针数组:
一个存储地址的数组。数组内部所有元素都是地址。
1)
int a = 10;
int b = 20;
int c = 30;
* p1 = &a;
*p2 = &b;
*p3 = &c;
int *arr[] = {p1,p2,p3}; // 数组元素为 整型变量 地址 // 从低地址向高地址存储
2)
int a[] = { 10 };
int b[] = { 20 };
int c[] = { 30 };
int *arr[] = { a, b, c }; // 数组元素为 数组 地址。
int a = 10;
int b = 20;
int c = 30;
int* p1 = &a;
int* p2 = &b;
int* p3 = &c;
int* arr[] = { p1,p2,p3 }; // 整型指针数组arr,存的都是整形地址。
printf("*(arr[0])=%d\n", **arr);
printf("**(*(arr + 0))=%d\n", *(*(arr + 0))); //arr[0] == * (arr+0)
/*
*(arr[0])=10
*(*(arr + 0))=10
*/
指针数组本质,是一个二级指针。
二维数组, 也是一个二级指针。
6.多级指针:
int a = 0;
int *p = &a; 一级指针是 变量的地址。
int **pp = &p; 二级指针是 一级指针的地址。
int ***ppp = &pp; 三级指针是 二级指针的地址。 // int **p[]
int ****pppp = &ppp; 四级指针是 三级指针的地址。 【了解】
......
多级指针,不能 跳跃定义!
对应关系:
ppp == &pp; 三级指针
*ppp == pp == &p; 二级指针
**ppp == *pp == p == &a 一级指针
***ppp == **pp == *p == a 普通整型变量
*p : 将p变量的内容取出,当成地址看待,找到该地址对应的内存空间。
如果做左值: 存数据到空间中。
如果做右值: 取出空间中的内容。
int a = 10;
int* p = &a;
int** pp = &p;
// int **pp = &(&a); 不允许!!!
int*** ppp = &pp;
printf("***ppp = %d\n", ***ppp);
printf("**pp = %d\n", **pp);
printf("*ppp = %d\n", *p);
printf("a = %d\n", a);
/*
***ppp = 10
**pp = 10
*ppp = 10
a = 10
*/
7.指针和函数:
栈 帧:
当函数调用时,系统会在 stack 空间上申请一块内存区域,用来供函数调用,主要存放 形参 和 局部变量(定义在函数内部)。
当函数调用结束,这块内存区域自动被释放(消失)。
传值和传址:
传值:函数调用期间,实参将自己的值,拷贝一份给形参。
传址:函数调用期间,实参将地址值,拷贝一份给形参。 【重点】
(地址值 --》 在swap函数栈帧内部,修改了main函数栈帧内部的局部变量值)
指针做函数参数: 调用时,传有效的地址值。
int swap2(int *a, int *b);
int swap2(char *a, char *b);
数组做函数参数:
```void BubbleSort(int arr[10]) == void BubbleSort(int arr[]) == void BubbleSort(int *arr) ```
传递不再是整个数组,而是数组的首地址(一个指针)。
所以,当整型数组做函数参数时,我们通常在函数定义中,封装2个参数。一个表数组首地址,一个表元素个数。
void BubbleSort (int arr[], int n)
指针做函数返回值:指针做函数返回值,不能返回【局部变量的地址值】。
int *test_func(int a, int b);
int m = 100; // 全局变量,对应空间消失==》程序结束
int* test_func(int a, int b)
{
return &m;
/*int p = 1234; // 局部变量,这个函数结束后,p的地址会释放,所以不能用这种方式来return
return &p;*/
}
int main(void)
{
int* ret = NULL;
ret = test_func(10, 20);
printf("ret = %d\n", *ret); // 100
}
数组做函数返回值:
C语言,不允许!!!! 只能写成指针形式。int* test_func(int a, int b)
会报错
8.指针和字符串:
1)字符串的定义方式:
char str1[] = {'h', 'i', '\0'}; 变量,可读可写
char str2[] = "hi"; 变量,可读可写
char *str3 = "hi"; 常量,只读 ,这里存的是字符串的地址
char *str4 = {'h','i','\0'}; // 错误!!!
str3变量中,存储的是字符串常量“hi”中首个字符‘h’的地址值。
str3[1] = 'H'; // 错误!!
char str1[] = "hello"; // {'h','e','l','l','o','\0'};
char* str2 = "hello"; // "hello"是一个字符串常量。不能修改
str1[0] = 'R';
/*str2[0] = 'R';*/ // 这一句话会报错
printf("str1 = %s\n", str1); // Rello
printf("str2 = %s\n", str2); // hello
2)当字符串(字符数组), 做函数参数时, 不需要提供2个参数。 因为每个字符串都有 ‘\0’。
【 练习:比较两个字符串: strcmp();实现】
比较 str1 和 str2, 如果相同返回0, 不同则依次比较ASCII码,str1 > str2 返回1,否则返回-1,按位比ACSSI码
//数组方式
int cmp(char* str1, char* str2)
{
int i = 0;
while (str1[i] == str2[i]) // *(str1+i)==*(str2+i)
{
if (str1[i] == '\0')
{
return 0; // 2字符串一样
}
i++;
}
return str1[i] > str2[i] ? 1 : -1;
}
//指针方式
int cmp_point(char* str1, char* str2)
{
while (*str1==*str2) // 这里面取的是首地址的值
{
if (str1 == '\0')
{
return 0; // 2字符串一样
}
str1++;
str2++;
}
return *str1 > *str2 ? 1 : -1;
}
int main(void)
{
char *str1 = "hello"; // 这个里面一定要写 *str1,不能写str1
char *str2 = "helll";
//int ret = cmp(str1,str2); // 这个里面不需要写&str1,因为str1本身就是代表地址,不需要再加取地址符
int ret = cmp_point(str1, str2);
if (ret == 0)
printf("str1=str2\n");
else if (ret == 1)
printf("str1>str2\n");
else if (ret == -1)
printf("str1<str2\n");
else
printf("异常\n");
}
【 练习:字符串拷贝:】
//src: 源 dst:目标
// 数组实现方式
void copy(char* src,char *dst)
{
int i = 0;
while (*(src + i))
{
*(dst + i) = *(src + i);
i++;
}
*(dst + i + 1) = '\0';
return 0;
}
// 指针实现方式
void copy_point(char* src, char* dst)
{
while (*src)
{
*dst = *src;
dst++;
src++;
}
*dst = '\0'; // 因为上面赋值之后指针都会向右移动,所以这里不需要写 *(dst+1)='\0';
return NULL;
}
int main(void)
{
char* src = "hello0";
char* dst[100] = { 0 };
//copy(src, dst);
//printf("char* dst = %s\n", dst);
copy_point(src, dst);
printf("char* dst = %s\n", dst);
}
【 练习:在字符串中查找字符出现的位置:找到字符串第一次出现时字符串和以及字符串后面的数字的值】
char* myStrch(char* str, char ch)
{
while (*str)
{
if (*str == ch)
{
return str;
}
str++;
}
return NULL;
}
// hellowrld --- 'o'
char* myStrch2(char* str, char ch)
{
int i = 0;
while (str[i])
{
if (str[i] == ch)
{
return &str[i];
}
i++;
}
return NULL;
}
int main(void)
{
char str[] = "hello world";
char ch = 'l';
char* ret = NULL;
ret = myStrch2(str, ch);
printf("ret = %s\n", ret);
}
【 练 习:字符串去空格】
// 指针
void qkg(char* str1, char* str2)
{
while (*str1)
{
if (*str1 != ' ')
{
*str2 = *str1;
str2++;
}
str1++;
}
*str2 = '\0';
}
// 数组
void qkg_array(char* str1,char* str2)
{
int i=0,j=0;
while (str1[i])
{
if (str1[i] != ' ')
{
str2[j] = str1[i];
j++;
}
i++;
}
}
int main(void)
{
char* str1 = "Hello World";
char str2[100]; // 字符串的定义,有*就没有[],有[]就没有*
/*qkg(str1, str2);*/
qkg_array(str1, str2);
printf("str2 = %s\n", str2);
// 如果字符串用char * 定义,后面取字符串的值就需要*,如果字符串用char []定义,后面取字符串就只需要字符串名字
}
带参数的main函数:
无参main函数: int main(void) == int main()
带参数的main函数:int main(int argc, char *argv[]) == int main(int argc, char **argv)
写法固定
参1:表示给main函数传递的参数的总个数。
参2:是一个数组!数组的每一个元素都是字符串 char * (字符串)
int main(int argc,char* argv[])
{
for (size_t i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
}
测试1(终端):
命令行中的中,使用gcc编译生成 可执行文件,如: test.exe
test.exe abc xyz zhangsan nichousha
-->
argc --- 5
test.exe -- argv[0]
abc -- argv[1]
xyz -- argv[2]
zhangsan -- argv[3]
nichousha -- argv[4]
测试2(VS):
在VS中。项目名称上 --》右键--》属性--》调试--》命令行参数 --》将 test.exe abc xyz zhangsan nichousha 写入。
-->
argc --- 5
test.exe -- argv[0]
abc -- argv[1]
xyz -- argv[2]
zhangsan -- argv[3]
nichousha -- argv[4]
【str 中 substr 出现次数】:
strstr函数: 在 str中,找substr出现的位置。
char *strstr(char *str, char *substr) -- #include <string.h>
参1: 原串
参2: 子串
返回值: 子串在原串中的位置。(地址值);
如果没有: NULL
char* ret = strstr("hellollolllolllo", "llo");
printf("ret = %s\n", ret); // ret = llollolllolllo
实 现:
int str_times(char* ch, char* substr)
{
int count = 0;
char *p = strstr(ch, substr); // llollolllollo
while (p!=NULL)
{
count++;
p += strlen(substr); // llolllollo
p = strstr(p, substr); //
}
return count;
}
int main(int argc,char* argv[])
{
int ret;
char* str = "hellollolllollo";
char sbustr[] = "llo";
ret = str_times(str, sbustr);
printf("ret = %d\n", ret);
}