01. 概述
指针变量:存地址的变量
内存单元:计算机中内存最小的存储单元。 —— 一个字节,每个内存单元都有唯一的编号,这个编程称为“地址”
02. 指针基础知识
- 指针也是一种数据类型,占用内存空间,用来操作内存地址。
- 指针变量指向谁,就把谁的地址赋值给指针变量
- “*”操作符操作的是指针变量指向的内存空间
#include <stdio.h>
int main()
{
//1. 基本认识
int a = 0;
printf("%p\n", &a); //%p 是打印a的地址,&a表示a在内存中的地址值
//int *代表是一种数据类型,int*指针类型,p才是变量名
int *p; //定义了一个指针类型的变量,可以指向一个int类型变量的地址
p = &a; //将a的地址赋值给变量p
printf("%d\n", *p); //p指向了a的地址,*p就是a
//2. 指针可以间接修改变量的值
int aa = 0;
int *pp = &aa;
*pp = 100;
printf("%d\n",aa); //结果:100
//3. 使用sizeof()测量指针的大小,sizeof()测的是指针变量指向存储地址的大小。
//注意: 在32位平台,所有的指针(地址)都是32位(4字节)
// 在64位平台,所有的指针(地址)都是64位(8字节)
int a1 = 0;
int *p1 = &a1;
printf("%d\n",sizeof(p1)); //结果:4或者8
//4. 指针也可以直接指向一个地址
int* p2 = 0x1234;
printf("p1 size:%d\n",sizeof(p2));
//5.const修饰指针变量
int a3 = 10;
int b3 = 20;
const int* p3 = &a3; //常量指针,指针指向的数据不能修改,指针的指向可以变
//*p3 = 100; //错误,不可修改
p3 = &b3; //正确
int* const p4 = &a3; //指针常量,指针指向的数据可以修改,指针的指向不能变
*p4 = 100; //正确
//p4 = &b3; //错误
const int* const p5 = &a3; //常量指针常量,指针指向的数据不能修改,指针的指向不能变
//*p5 = 100; //错误
//p5 = &b3; //错误
return 0;
}
注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。
03. 空指针、野指针、万能指针
空指针
空指针:指向NULL的指针
注意:不允许向NULL和非法地址拷贝内存
#include <stdio.h>
int main()
{
int *p = NULL; //空指针
//*p = 10; //错误
int *pp = 0x1122; //直接指向地址
//*pp = 20; //错误,非法访问
return 0;
}
野指针
野指针的产生:
- 指针变量未初始化
- 指针释放后未置空
- 返回了局部变量的指针
野指针的避免:养成良好的编程习惯
#include <stdio.h>
int main()
{
//1.未初始化
int *p1;
//2.未置空
int *p2 = (int*)malloc(sizeof(int)*2);
free(p2);
//p2 = NULL //要置空
//3.超越变量作用域
int func();
//int *p = func(); //错误
return 0;
}
//返回局部变量的指针
int func()
{
int a = 10;
return &a;
}
万能指针
万能指针:void *万能指针可以指向任意变量的内存空间
#include <stdio.h>
int main()
{
void *p = NULL; //万能指针
int a = 10;
//指向变量时,最好转换为void *
p = (void *)&a;
//使用指针变量指向的内存时,转换为int *
*( (int *)p ) = 11;
printf("a = %d\n", a);
return 0;
}
04. 指针和数组
注意:数组名就是 数组首元素的地址,是一个常量
#include <stdio.h>
int main()
{
int a[] = {1,2,3};
int b[3];
//b = a; //错误,数组名是地址常量
int *p = a; //指针是变量。可以用数组名给指针赋值
p[0] = 10; //等价于 a[0] = 10;
*(p+1) = 20; //等价于 p[1] = 20;
//指针数组,它是一个数组,数组的每个元素都是指针类型
int a = 1;
int b = 2;
int c = 3;
int *p[] = {&a,&b,&c};
*(p[0]) = 10; //a=10
*(p[1]) = 20; //b=20
*(p[2]) = 30; //c=30
return 0;
}
05. 指针加减运算和指针步长
指针计算不是简单的数据相加减
指针运算:
- 如果是一个int *,+1的结果是增加一个int的大小
- 如果是一个char *,+1的结果是增加一个char大小
#include <stdio.h>
int main()
{
int a = 10;
int *p = &a;
p += 2;//移动了2个int
//通过改变指针指向操作数组元素
int arr[] = {1,2,3,4,5,6,7,8,9};
int p2 = arr;
*p2 = 10; //p2指向的是arr的首地址arr[0],arr[0] = 10
p2++; //现在p2+1了,指向的是arr[1]
//指针的步长
//char类型占一个字节,所以跳跃1字节数
char* p = NULL;
printf("%d\n",p); // 0
printf("%d\n",p+1); //1
//double类型占8个字节,所以跳跃8字节数
double* pp = NULL;
printf("%d\n",pp); // 0
printf("%d\n",pp+1); //1
return 0;
}
06. 二级指针和多级指针
二级指针:存放的是1级指针的地址
三级指针:存放的是2级指针的地址
…
C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。三级指针以上基本用不着。
int a = 10;
int *p = &a; //一级指针
*p = 100; //*p就是a
int **pp = &p;
//*pp就是p
//**pp就是a
int ***ppp = &pp;
//*ppp就是pp
//**ppp就是p
//***ppp就是a
07. 指针和函数
函数形参改变实参的值
#include <stdio.h>
void swap1(int x, int y)
{
int tmp;
tmp = x;
x = y;
y = tmp;
printf("x = %d, y = %d\n", x, y);
}
void swap2(int *x, int *y)
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 3;
int b = 5;
swap1(a, b); //值传递
printf("a = %d, b = %d\n", a, b);
a = 3;
b = 5;
swap2(&a, &b); //地址传递
printf("a2 = %d, b2 = %d\n", a, b);
return 0;
}
数组名做函数参数,函数的形参会退化为指针
#include <stdio.h>
//void printArrary(int a[10], int n)
//void printArrary(int a[], int n)
void printArrary(int *a, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d, ", a[i]);
}
printf("\n");
}
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int n = sizeof(a) / sizeof(a[0]);
//数组名做函数参数
printArrary(a, n);
return 0;
}
指针做为函数的返回值
#include <stdio.h>
int a = 10;
int *getA()
{
return &a;
}
int main()
{
*( getA() ) = 111;
printf("a = %d\n", a);
return 0;
}
指针做函数参数
指针做函数参数,具备输入和输出特性:
- 输入特性:在主调函数中分配内存,被调函数使用
- 输出特性:在被调函数中分配内存,主调函数使用
输入特性
#include <stdio.h>
void func01(char* p)
{
//被调函数中使用
strcpy(p,"hello");
}
int main()
{
//主调函数在栈上分配了内存,
char buf[1024] = {0};
func(buf); //输入特性
return 0;
}
输出特性
#include <stdio.h>
void func01(char** p)
{
//在被调函数 堆区中分配内存
char* str = (char*)malloc(sizeof(char)*64);
memset(str,0,64); //置0
strcpy(str,"hello");
*p = str;
}
int main()
{
char* p = NULL;
func01(&p);
//主调函数使用
printf("%s\n",p);
return 0;
}
08. 指针和字符串
字符指针
#include <stdio.h>
int main()
{
char str[] = "hello world";
char *p = str;
*p = 'm';
p++;
*p = 'i';
printf("%s\n", str);
p = "mike jiang";
printf("%s\n", p);
char *q = "test";
printf("%s\n", q);
return 0;
}
字符指针做函数参数
#include <stdio.h>
void mystrcat(char *dest, const char *src)
{
int len1 = 0;
int len2 = 0;
while (dest[len1])
{
len1++;
}
while (src[len2])
{
len2++;
}
int i;
for (i = 0; i < len2; i++)
{
dest[len1 + i] = src[i];
}
}
int main()
{
char dst[100] = "hello mike";
char src[] = "123456";
mystrcat(dst, src);
printf("dst = %s\n", dst);
return 0;
}
指针数组做为main函数的形参
-
main函数是操作系统调用的,第一个参数标明argv数组的成员数量,argv数组的每个成员都是char *类型
-
argv是命令行参数的字符串数组
-
argc代表命令行参数的数量,程序名字本身算一个参数
#include <stdio.h>
//argc: 传参数的个数(包含可执行程序)
//argv:指针数组,指向输入的参数
int main(int argc, char *argv[])
{
//指针数组,它是数组,每个元素都是指针
char *a[] = { "aaaaaaa", "bbbbbbbbbb", "ccccccc" };
int i = 0;
printf("argc = %d\n", argc);
for (i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
09. 字符串指针强化
//字符串基本操作
//字符串是以0或者'\0'结尾的字符数组,(数字0和字符'\0'等价)
void test01(){
//字符数组只能初始化5个字符,当输出的时候,从开始位置直到找到0结束
char str1[] = { 'h', 'e', 'l', 'l', 'o' };
printf("%s\n",str1);
//字符数组部分初始化,剩余填0
char str2[100] = { 'h', 'e', 'l', 'l', 'o' };
printf("%s\n", str2);
//如果以字符串初始化,那么编译器默认会在字符串尾部添加'\0'
char str3[] = "hello";
printf("%s\n",str3);
printf("sizeof str:%d\n",sizeof(str3));
printf("strlen str:%d\n",strlen(str3));
//sizeof计算数组大小,数组包含'\0'字符
//strlen计算字符串的长度,到'\0'结束
//那么如果我这么写,结果是多少呢?
char str4[100] = "hello";
printf("sizeof str:%d\n", sizeof(str4));
printf("strlen str:%d\n", strlen(str4));
//请问下面输入结果是多少?sizeof结果是多少?strlen结果是多少?
char str5[] = "hello\0world";
printf("%s\n",str5);
printf("sizeof str5:%d\n",sizeof(str5));
printf("strlen str5:%d\n",strlen(str5));
//再请问下面输入结果是多少?sizeof结果是多少?strlen结果是多少?
char str6[] = "hello\012world";
printf("%s\n", str6);
printf("sizeof str6:%d\n", sizeof(str6));
printf("strlen str6:%d\n", strlen(str6));
}
字符串拷贝实现
//拷贝方法1
void copy_string01(char* dest, char* source ){
for (int i = 0; source[i] != '\0';i++){
dest[i] = source[i];
}
}
//拷贝方法2
void copy_string02(char* dest, char* source){
while (*source != '\0' /* *source != 0 */){
*dest = *source;
source++;
dest++;
}
}
//拷贝方法3
void copy_string03(char* dest, char* source){
//判断*dest是否为0,0则退出循环
while (*dest++ = *source++){}
}
字符串反转实现
void reverse_string(char* str){
if (str == NULL){
return;
}
int begin = 0;
int end = strlen(str) - 1;
while (begin < end){
//交换两个字符元素
char temp = str[begin];
str[begin] = str[end];
str[end] = temp;
begin++;
end--;
}
}
void test(){
char str[] = "abcdefghijklmn";
printf("str:%s\n", str);
reverse_string(str);
printf("str:%s\n", str);
}
字符串的格式化
sprintf()
void test(){
//1. 格式化字符串
char buf[1024] = { 0 };
sprintf(buf, "你好,%s,欢迎加入我们!", "John");
printf("buf:%s\n",buf);
memset(buf, 0, 1024);
sprintf(buf, "我今年%d岁了!", 20);
printf("buf:%s\n", buf);
//2. 拼接字符串
memset(buf, 0, 1024);
char str1[] = "hello";
char str2[] = "world";
int len = sprintf(buf,"%s %s",str1,str2);
printf("buf:%s len:%d\n", buf,len);
//3. 数字转字符串
memset(buf, 0, 1024);
int num = 100;
sprintf(buf, "%d", num);
printf("buf:%s\n", buf);
//设置宽度 右对齐
memset(buf, 0, 1024);
sprintf(buf, "%8d", num);
printf("buf:%s\n", buf);
//设置宽度 左对齐
memset(buf, 0, 1024);
sprintf(buf, "%-8d", num);
printf("buf:%s\n", buf);
//转成16进制字符串 小写
memset(buf, 0, 1024);
sprintf(buf, "0x%x", num);
printf("buf:%s\n", buf);
//转成8进制字符串
memset(buf, 0, 1024);
sprintf(buf, "0%o", num);
printf("buf:%s\n", buf);
}
sscanf()函数
格式 | 作用 |
---|---|
%*s或%*d | 跳过数据 |
%[width]s | 读指定宽度的数据 |
%[a-z] | 匹配a到z中任意字符(尽可能多的匹配) |
%[aBc] | 匹配a、B、c中一员,贪婪性 |
%[ ^a ] | 匹配非a的任意字符,贪婪性 |
%[ ^a-z ] | 表示读取除a-z以外的所有字符 |
//1. 跳过数据
void test01(){
char buf[1024] = { 0 };
//跳过前面的数字
//匹配第一个字符是否是数字,如果是,则跳过
//如果不是则停止匹配
sscanf("123456aaaa", "%*d%s", buf);
printf("buf:%s\n",buf);
}
//2. 读取指定宽度数据
void test02(){
char buf[1024] = { 0 };
//跳过前面的数字
sscanf("123456aaaa", "%7s", buf);
printf("buf:%s\n", buf);
}
//3. 匹配a-z中任意字符
void test03(){
char buf[1024] = { 0 };
//跳过前面的数字
//先匹配第一个字符,判断字符是否是a-z中的字符,如果是匹配
//如果不是停止匹配
sscanf("abcdefg123456", "%[a-z]", buf);
printf("buf:%s\n", buf);
}
//4. 匹配aBc中的任何一个
void test04(){
char buf[1024] = { 0 };
//跳过前面的数字
//先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
sscanf("abcdefg123456", "%[aBc]", buf);
printf("buf:%s\n", buf);
}
//5. 匹配非a的任意字符
void test05(){
char buf[1024] = { 0 };
//跳过前面的数字
//先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
sscanf("bcdefag123456", "%[^a]", buf);
printf("buf:%s\n", buf);
}
//6. 匹配非a-z中的任意字符
void test06(){
char buf[1024] = { 0 };
//跳过前面的数字
//先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
sscanf("123456ABCDbcdefag", "%[^a-z]", buf);
printf("buf:%s\n", buf);
}