IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C基础(四)指针的使用 -> 正文阅读

[C++知识库]C基础(四)指针的使用

一、一级指针

指针也是一个变量,指针存放的内容是一个内存地址,该地址指向一块内存空间。

1.1 指针变量的定义

一级指针变量的数据类型会在基本数据了类型之后多了一个*号,指针变量只能存放内存地址(一个16进制的数),不能将一个基本数据类型直接赋值给一个指针变量。

如果要取出一级指针变量指向的内存地址所对应的值的话,可以通过在指针变量前加一个*号来获取

int *p;//表示定义一个指针变量p, 类型是int *;
*p;//代表获取该指针指变量指向的内存块对应的实际数据

int *p = 100; //错误, 只能指向内存地址, 是一个16进制的数
*p =100 ;  //正确, 因为*p操作的是变量的值.

1.2 &取地址运算符

通过&符号可以取得一个变量在内存当中的地址,然后就可以赋值给指针变量了。

#include<stdio.h>

int main()
{
	int a = 100;
	int *p;// 定义了一个int *类型的指针变量,名字叫做p
	p = &a;// 给指针变量赋值,把a的内存地址赋值给指针变量p
	*p = 200;//修改a的值,效果等于直接给a赋值, *p代表指针指向变量的值,而p代表指向变量的内存地址
	printf("p=%p,*p=%d,a=%d\n",p,*p,a); 

    // 通常为了书写方便,可以直接这样给指针变量赋值
    // int *p1 = &a;
    
    // 直接修改a变量的值,内存地址并不会修改
    a = 50;
    // 查看*p的值
    printf("p=%p,*p=%d,a=%d\n",p,*p,a);

	return 0;
}

两次输出结果如下:
在这里插入图片描述
可以看到*p和a变量的值是一样的,并且修改值并不会影响p指针变量指向的内存地址。

1.3 无类型指针

定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将void *转化为其他类型指针,也可以用(void *)将其他类型指针强制转化为void类型指针。
void *p 他可以指向任意类型的内存地址

1.4 空指针与野指针

NULL在C语言中的定义为(void *)0 ,它是一个宏常量, #define NULL 0 .如果一个指针变量没有明确指向一块内存地址, 那么就把这个变量指向NULL,这个指针就是空指针,空指针是合法的,例如:

int *p = NULL;
// 或者
int *p;
p = NULL;

指向NULL的指针叫空指针,没有具体指向任何变量地址的指针(也就是没有初始化值的指针)叫野指针。

#include<stdio.h>

int main()
{
	// 定义了一个int *类型的指针变量p
	int *p;  
	// 如果指针变量没有初始化就使用,那就是野指针,
	// 由于它不知道指向的内存地址是啥,所以无法修改这块内存地址所对应的数值, 程序执行的时候会报错
	*p = 100; 

	return 0 ;
}

1.5 指针的兼容性

指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个double *赋值给int * ,它是不会自动类型转换的.
原则上一定是相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向另一种类型的变量地址。

1.6 常量指针与指针常量

1、指向常量的指针(常量指针)
常量指针的const书写在指针类型之前,例如:
const char *p;
定义一个指向常量的指针, 但是这个指针不能通过*p的方式取修改变量的值了。但是可以通过*p的方式读取变量的值。

#include<stdio.h>

int main()
{
	int a = 10; //定义一个变量

	// 定义一个指向int类型的地址的指针变量,指向a变量的内存地址
	const int *p = &a;   // 指针类型是 const int *
	*p = 100; // 编译会通过不了,常量指针的常量不能改变, 但是直接给a变量赋值还是可以的.只是不允许通过*p的方式
	printf("a=%d\n",*p);
	return 0;
}

2、指针常量
指针常量的const书写在指针类型之后,例如:
char * const p;
定义一个指针常量,一旦初始化之后其内容不可改变,也就是说不能再指向其他变量的地址了。但是可以通过*p的方式改变变量的值。

#include<stdio.h>

int main()
{
	int a = 10; //定义一个变量

	//定义一个指针常量,指向a变量的内存地址
	int *const p = &a; //指针类型是 int *const

	int b = 20; // 允许修改变量
	p = (int *)&b;//编译通不过,因为p是一个常量,不能被修改,指针常量的指针不能修改

	printf("%d\n",*p);

	return 0;
}

原则上在C语言中常量指针的常量不能修改、指针常量的指针不能修改;但是C语言中通过const定义的常量是有问题的,因为可以通过指针变量来间接的修改常量的值,所以在C语言中用#define常量比较多,而C++的const是没办法改的。

1.7 指针与数组的关系

一个变量有地址,一个数组包含若干个元素,每个元素在内存中都有地址,而且是连续的内存地址.也就是说如果数组是char类型的,那么每个元素的内存地址就是相差1, 如果是int类型的数组, 那么元素间的内存地址是相差4,例如:

int a[10];
int *p = a;

p和&a[0]的地址,以及a数组名的地址都是一样的。 也就是说数组名的地址等于数组首元素的内存地址。

#include<stdio.h>

int main()
{
	char a[10];
	printf("a=%p\na[0]=%p\na[1]=%p\na[2]=%p\n",a,&a[0],&a[1],&a[2]);

	printf("========================\n");

	int b[10];
	printf("b=%p\nb[0]=%p\nb[1]=%p\nb[2]=%p\n",b,&b[0],&b[1],&b[2]);
	return 0;
}

输出结果如下:

a=0x16efa760e
a[0]=0x16efa760e
a[1]=0x16efa760f
a[2]=0x16efa7610
========================
b=0x16efa75e4
b[0]=0x16efa75e4
b[1]=0x16efa75e8
b[2]=0x16efa75ec

从结果也可以看出char数组的每个元素内存地址相差1, int数组的元素内存地址相差4.

注意:当指针变量指向一个数组名的时候,C语言语法规定指针变量名可以当做数组名使用.但是指针变量的sizeof不是数组的sizeof, 指针变量的sizeof在64位系统是8个字节, 32位系统是4个字节.

#include<stdio.h>

int main() {
    int a[] = {1, 2, 3, 4, 5};
    int *p; // 指针变量
    // 数组可以直接赋值给指针变量,不用&取地址,因为变量名a的地址就是数组首元素的内存地址
    p = a;

    // 如果指针指向的是数组,那么变量名可以当做数组用
    p[3] = 100;

    // 数组的长度和指针变量的长度是不一样的
    printf("数组长度=%lu字节,指针变量长度=%lu字节\n", sizeof(a), sizeof(p));

    // 操作指针变量来操作数组元素
    int i;
    for (i = 0; i < 5; i++) {
        printf("a[%d]=%d\n", i, p[i]);// 和操作数组一样操作元素
    }
    return 0;
}

输出结果如下:

数组长度=20字节,指针变量长度=8字节
a[0]=1
a[1]=2
a[2]=3
a[3]=100
a[4]=5

注意:指针变量如果指向的不是数组名或者数组的首元素的内存地址,那么用指针去取数组的元素的时候是有区别的,假设指针指向的是数组下标为2的元素的内存地址, 那么p[0] 就不再是等于a[0]了, 而是等于a[2]了, 同理p[1]就应该等于a[3],其他依次类推。

1.8 指针位移运算

指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数做为倍数的运算。
例如:char *p;当执行p++;后,移动的字节数是sizeof(char)这么多的字节数;如果是int *p1;那么p1++后移动的字节数就是sizeof(int)

#include<stdio.h>
int main()
{
    int a[5] = {0};
    int *p = a;//指向数组名的指针,就是数组首元素的地址
    *p = 100;// 给指针变量对应地址的变量赋值100,相当于p[0] = 100;

    printf("移动前指针位置:%p\n",p);

    //移动指针
    //移动2,表示移动了sizeof(int) *2 个字节,对应就是数组下标=2的元素内存地址
    p += 2;
    *p = 20;//修改a[2]的值为20
    printf("移动后指针位置:%p\n",p);

    int i;
    for(i = 0 ;i < 5; i++)
    {
        printf("a[%d]=%d\n",i,a[i]);
    }
    return 0;
}

结果如下:

移动前指针位置:0x16efbb600
移动后指针位置:0x16efbb608
a[0]=100
a[1]=0
a[2]=20
a[3]=0
a[4]=0

可以看到指针+2后,移动前后的内存地址刚好相差8个Byte,也就是2个int元素的占用的内存大小.

1.9 指针与char数组

在C语言中所有的数据类型都可以当做是一个char的数组.

#include<stdio.h>

int main()
{
	int a = 0x12345678; //定义一个16进制数,int占4个字节,相当于char数组长度是4个BYTE

	char *p = (char *)&a;//定义char * 的指针,指向a的地址

	int i = 0 ;
	for(i =0;i < sizeof(a); i++)
	{
		printf("a[%d]=%x\n",i,p[i]);//由于是小端对齐,所以输出结果是78,56,34,12倒着输出的.内存地址从左->右表示从高->底
	}


	return 0;
}

练习-将一个int数转成ip地址
我们知道一个int占4个字节,刚好对应4个char, 对于unsigned char的取值范围就是0~255, 所以刚好和ip地址每段的取值符合.所以可以将int数转成ip地址。

#include<stdio.h>

int main()
{
	int a = 987654321;

	unsigned char *p = (unsigned char *)&a; //定义unsigned char *类型的指针变量p 对应a变量的地址,这里其实是指针强转
	
	//在C语言中基本数据类型可以当做char数组对待,所以可以这样操作
	printf("%u.%u.%u.%u\n",p[3],p[2],p[1],p[0]); // 结果为: 58.222.104.177

	return 0;
}

直接ping 987654321 对应的ip地址就是上面用程序求出的ip: 58.222.104.177
在这里插入图片描述

练习-将一个字符串IP转成unsigned int类型.
这个案例就是上面案例的反过程, 先将字符串中每一段的数据取出来,然后操作指针来赋值.

#include<stdio.h>

int main()
{
	char a[] = "192.168.1.1";
	unsigned int ip = 0;
	unsigned char * p = (unsigned char *) &ip;

	//先从字符串中取值
	int a1,a2,a3,a4;
	sscanf(a,"%d.%d.%d.%d",&a1,&a2,&a3,&a4);

	//小端对齐赋值
	p[0] = a4;
	p[1] = a3;
	p[2] = a2;
	p[3] = a1;

	printf("ip=%u\n",ip); // ip=3232235777
	return 0;
}

如下所示,对数字3232235777进行ping时,查看的ip刚好就是192.168.1.1
在这里插入图片描述

练习-使用指针对二维数组进行排序

#include<stdio.h>

int main()
{
	int a[2][3] = {{10,21,13},{42,5,9}}; //二维数组

	int *p = (int *)a; // 定义一个指针, 指向数组a
	int i,j;

    //下面通过操作指针的方式来排序, 由于内存是连续的,所以可以当做一维数组看待
	int size = sizeof(a) / sizeof(a[0][0]); // 6
	for(i = 0 ; i < size; i++)
	{
		for(j=0 ; j< size - i -1 ; j++)
		{
			if(p[j] > p[j+1])
			{
       			// 直接通过指针操作内存地址对应的值
				int temp = p[j];
				p[j] = p[j+1];
				p[j+1] = temp;
			}
		}
	}
	//输出排序后的结果
	for( i =0 ; i <sizeof(a) /sizeof(a[0]) ; i++)
	{
		for( j =0; j<sizeof(a[0]) / sizeof(a[0][0]); j++)
		{
			printf("%d,",a[i][j]); // 5,9,10,13,21,42,
		}
	}
	
	return 0;
}

练习-使用指针对字符串取反操作

int main() {
    char str[20] = "hello world";

    char *start = str;//&(str[0]);  //创建首指针
    char *end = start + strlen(str) - 1; //创建尾指针, 通过指针偏移到尾部

    while (start < end) {
        // 通过指针操作值
        char tmp = *start;
        *start = *end;
        *end = tmp;
        // 偏移首位指针
        start++;
        end--;
    }
    printf("%s\n", str); // dlrow olleh

    return 0;
}

1.10 指针数组

由于指针是一个变量,所以也可以以数组的形式出现。 指针数组的字节长度不受数据类型的影响,而是受到操作系统的影响, 在32位系统中,一个指针的长度是4个BYTE, 64位系统是8个BYTE, 所以32位系统中指针数组的长度等于 元素个数*4 ; 64位系统中指针数组的长度等于元素个数*8.

#include<stdio.h>

int main()
{
	//定义一个int *类型的10个元素的指针数组
	int *a[10] = {NULL};

	//定义一个char *类型的10个元素的指针数组
	char *b[10] = {NULL};

	printf("%lu,%lu\n",sizeof(a),sizeof(b)); // 80,80

	return 0;
}

指针数组元素的赋值

#include<stdio.h>

int main()
{
	int *a[10] = {NULL};
	
	int i = 5;
	a[0] = &i; //取i的地址给a[0]指针

	*a[0] = 100; //修改i的值

	printf("i=%d\n",i); // i=100
	
	return 0;
}

二、二级和多级指针

指针就是一个变量,既然是变量就也就存在内存地址,所以可以定义一个指向指针的指针变量。二级指针的定义会比一级指针多一个*;
二级指针指向的是一级指针的地址, 一级指针指向的是变量的地址, 如下图所示:
在这里插入图片描述
换成代码就是:

int a =  10;
int *p = &a; //一级指针指向变量a
int **pp = &p; //二级指针指向一级指针变量p

//反过来就是修改变量的值
*pp  = NULL; // 表示将1级指针变量的值赋值为NULL;
**pp  = 100; //表示把变量a的值赋值为100; 

2.1 二级指针与一级指针数组的关系

一级指针数组里面存放的元素都是一级指针变量, 而指针数组的名字就是数组首元素的内存地址, 也就是一级指针的内存地址, 所以可以用二级指针的来指向.当指针指向数组的时候就可以当做数组本身来使用了.

int main()
{
	int *a[3] = {NULL};//一级指针数组
	int x,y,z;
	a[0] = &x; //给数组元素赋值
	a[1] = &y;
	a[2] = &z;

	int **p = a;//二级指针指向数组名,数组名等效于指向&a[0],而a[0]是个一级指针.所以指向一级指针地址的指针那就是二级指针了.
		
	//指针指向数组就可以当做数组用

	int i;
	for(i=0;i<3;i++)
	{
		printf("p[%d]=%p\n",i,p[i]); //打印数组元素,数组元素是一级指针
	}
	
	*p[0] = 100;//等效于 *a[0] =100; 就是将0下标的指针指向的变量的值修改为100,因为p[0]=a[0]=一级指针,然后加*就是修改值
	*p[1] = 200;
	*a[2] = 300;
	printf("x=%d,y=%d,z=%d\n",x,y,z);

	
	return 0;
}

结果如下:

p[0]=0x16b1735f8
p[1]=0x16b1735f4
p[2]=0x16b1735f0
x=100,y=200,z=300

2.2 多级指针

每多加一级指针, 指针类型的*号就会多一个.

#include<stdio.h>

int main()
{
	int a = 10;
	int *p = &a; // 一级指针
	int **pp = &p; //二级指针
	int ***ppp = &pp; //三级指针
	int ****pppp = &ppp; //四级指针
	int *****ppppp = &pppp; //五级指针

	*****ppppp = 100;//修改a的值
	printf("a=%d\n",a);
	
	return 0;
}

三、指针在函数中的应用

3.1 在函数参数中使用指针

在C语言中规定,如果想通过函数内部修改外部实参的值,那么就需要给函数的参数传递实参的地址,否则是无法修改的。因为函数内对形参的改变不会影响到外部实参的值。

#include<stdio.h>

//错误的交换姿势
void swap1(int a, int b )
{
	int temp = a;
	a = b;
	b = temp;
}


//正确的交换姿势,形参定义为指针, 可以接收变量的地址
void swap2(int *a,int *b)
{
	//下面的交换就是交换地址对应的变量的值,地址是不会改变的,这样可以保证唯一性
	int temp = *a; //取a指针变量对应的地址的变量的值
	*a = *b; //取b指针变量对应的地址的变量的值,赋值给a变量
	*b = temp;
}

int main()
{
	int a = 1;
	int b = 2;
	swap1(a,b);
	printf("swap1->a=%d,b=%d\n",a,b);
	
	swap2(&a,&b); //传递变量的地址
	printf("swap2->a=%d,b=%d\n",a,b);

	return 0;
}

结果如下:

swap1->a=1,b=2
swap2->a=2,b=1

可以看到swap1并没有交换成功, 而swap2成功了.

3.2 数组名作为函数的参数

C语言规定当一个数组名作为函数的形参的时候,它就变成了指针变量名了,而不再是数组名了.
所以下面3种参数的写法其实是一样的

void test1(int a[10]);
void test2(int a[]);
void test3(int  *a);  //第三种写法常用

//第三种写法如果不想被函数的内部修改元素的值, 那就可以这样定义
void test3(const  int *a);  //相当于指向常量的指针 ,当然别人想改还是可以强转来修改的

注意:如果把一个数组名作为函数的参数,那么在函数内部就不知道这个数组的元素个数了,因为sizeof算出来的是指针的大小, 32位系统是4个BYTE, 64位是8个BYTE, 所以如果想知道数组的长度,就需要增加一个参数来标明这个数组的大小. 除非数组是一个字符串, 那么可以不用传递长度.因为字符串可以有strlen方法来获取长度

#include<stdio.h>

//void test(int a[10])
//void test(int a[]);
void test(int *a) //如果传入的是数组名,就变成了指针变量,所以可以这样写
{
    printf("test->sizeof=%lu\n",sizeof(a)); // test->sizeof=8

    //操作指针就可以操作数组
    a[2]  = 100;
}

int main()
{
    int a[10] = {0,1,2,3,4,5,6,7,8,9};
    test(a); //将数组名传入
    printf("main->sizeof=%lu\n",sizeof(a)); // main->sizeof=40 

    int i;
    for(i=0;i<sizeof(a)/sizeof(a[0]);i++)
    {
        printf("a[%d]=%d,",i,a[i]);//a[0]=0,a[1]=1,a[2]=100,a[3]=3,a[4]=4,a[5]=5,a[6]=6,a[7]=7,a[8]=8,a[9]=9,
    }

    return 0;
}

3.3 函数指针

所谓函数指针就是指向函数内存地址的指针。一个函数在编译的时候会分配一个入口地址,这个入口地址就是函数的指针,函数名就是函数的入口地址。

函数指针的定义方式:
函数的返回值类型(*指针变量名称)(参数列表)
例如:
int(*p)(int) 定义一个指向int func(int n)类型的函数地址的指针.

函数可以通过函数指针来调用.
int(*p)()代表一个指向函数的指针,但是没有固定指向哪一个函数, 这个可以动态指针.

void man() {
    printf("抽烟\n");
    printf("喝酒\n");
    printf("打牌\n");
}

void woman() {
    printf("化妆\n");
    printf("逛街\n");
    printf("网购\n");
}

int main() {
    void (*p)(); //定义一个无返回值,无参数的函数指针,指针变量名是p
    int i = 0;
    scanf("%d", &i); // 读取用户输入
    if (i == 0)
        p = man; //指向man函数
    else
        p = woman; //指向woman函数
    p(); //通过指针调用函数
    return 0;
}

3.4 回调函数

将函数的指针作为另一个函数的参数时,这个参数就被称为回调函数。

#include <stdio.h>

int max(int a, int b) {
    if (a > b)
        return a;
    else
        return b;
}

int add(int a, int b) {
    return a + b;
}

//参数1是一个函数指针(回调函数)
void func(int(*p)(int, int), int a, int b) {
    int res = p(a, b);//调用p指针对应的函数执行
    printf("%d\n", res);

}

int main() {
    int i = 0;
    scanf("%d", &i); // 接收控制台输入的值
    if (i == 0)
        func(max, 10, 20); //传参的时候,直接传入函数的名字,因为函数名就是地址,可以用函数指针接收
    else
        func(add, 10, 20);
    return 0;
}

3.5 指针数组作为mian函数的形参使用

int main(int argc, char ** argv)  //参数2是二级指针,可以接收一级指针数组, 写法等效于char *argv[]

main函数是操作系统调用的,所以main函数的参数也是操作系统在调用的时候自动填写的,其中argc打包命令行参数的数量, argv代表命令行的具体参数.

下面写一个计算 2个数的和的程序,通过main函数的参数来完成.

#include<stdio.h>
#include<stdlib.h>

int main(int argc,char **args)
{
	if(argc <=2)
	{
		printf("参数不足,使用方式:%s 数字1 数字2\n",args[0]);
		return 0;
	}

	int a = atoi(args[1]); //char *类型可以直接访问字符串
	int b = atoi(args[2]);
	printf("sum=%d\n",a+b);

	return 0;
}

下面是一个四则运行实例代码

#include<stdio.h>
#include<stdlib.h>

int main(int argc,char **args)
{
	if(argc <=3)
	{
		printf("参数不足,使用方式:%s 数字1  运算符  数字2\n",args[0]);
		return 0;
	}

	int a = atoi(args[1]); //char *类型可以直接访问字符串
	char *c  = args[2];   //接收运算符, 注意char*指向的是字符串而不是char , 是双引号的字符串内容
	int b = atoi(args[3]);
	
	switch(c[0])  //取字符串首字符
	{
		  case '+':
			printf("%d%s%d=%d\n",a,c,b,a+b);
			break;
       	    case '-':
                    printf("%d%s%d=%d\n",a,c,b,a-b);
                    break;
    	    case '*':   //注意乘法在控制台输入的时候需要转义 \* ,否则在Linux上代表是所有的意思
                    printf("%d%s%d=%d\n",a,c,b,a*b);
                    break;
    	    case '/':
                    printf("%d%s%d=%d\n",a,c,b,a/b);
                    break;
	}
	return 0;
}

四、内存操作与指针

memset、memcpy、memmove这3个函数分别是内存的设置、内存的拷贝和内存的移动。使用时需要引入string.h头文件。

4.1 memset

表示设置内存,接收3个参数

void *memset(void *s, int c,  size_t n);

参数1:是一个无类型的指针, 用于接收一个内存首地址,如果要操作数组,那就传数组名,因为数组名对应的是首元素的内存地址. 可以被指针变量接收.
参数2:要设置的值,如果填0就是0.
参数3:表示这块内存的大小,单位是字节
例如:

#include<stdio.h>
#include<string.h>

int main()
{
	int a[5] = {1,2,3,4,5};
	//清空a数组的值
	memset(a,0,sizeof(a));

	int i;
	for(i=0;i<5;i++)
	{
		printf("a[%d]=%d,",i,a[i]); // a[0]=0,a[1]=0,a[2]=0,a[3]=0,a[4]=0,

	}

	return 0;
}

4.2 memcpy

用于在2块内存之间拷贝数据,接收3个参数.

void *memcpy(void *dest,  const void *src,   size_t  n);

参数1:目标地址的指针变量
参数2:源地址的指针变量, 且不能修改源地址变量的值,他是const修饰的.
参数3:拷贝多少内容,单位字节

#include<stdio.h>
#include<string.h>

int main()
{
	int a[5] = {1,2,3,4,5};

	//拷贝到b数组
	int b[10] = {0};
	memcpy(b,a,sizeof(a)); //参数1和2都是无类型的,所以其他数据类型,包括字符串都可以使用.
	int i;
	for(i=0;i<10;i++)
	{
		printf("b[%d]=%d,",i,b[i]);//b[0]=1,b[1]=2,b[2]=3,b[3]=4,b[4]=5,b[5]=0,b[6]=0,b[7]=0,b[8]=0,b[9]=0,

	}

	return 0;
}

再来看看字符串的拷贝

#include<stdio.h>
#include<string.h>

int main()
{
	char a[] = "hello"; //定义一个字符串
	char b[10] = {0}; //定义一个char数组,char数组元素为0时,用%c输出的时候是看到不到的.
      //拷贝
	memcpy(b,a,sizeof(a));

	int i;
	for(i=0;i<10;i++)
	{
           //输出b的每个元素的值
		printf("b[%d]=%c\n",i,b[i]);
	}
  //拷贝完后, b就变成字符串了
	printf("b=(%s)\n",b);
	return 0;

}

使用memcpy的时候,一定要确保内存没有重叠区域.
例如 :

int a[10] ={0}; 
memcpy(&a[3], &a[0], 20);  //拷贝20个字节也就是5个int数据

将a[0]之后的5个int数据拷贝到a[3]之后的5个int数据,他们中间下标3和4之间存在内存重叠区域.

4.3 memmove

表示内存移动, 参数也memcpy一样,用法也一样.

#include<stdio.h>
#include<string.h>

int main()
{
	int a[10] = {0,1,2,3,4,5,6,7,8,9};
	int b[10] = {0};
    //移动a的内存到b,并不会改变a的内存
	memmove(b,a,sizeof(a));

	int i;
	for(i=0;i<10;i++)
	{
		printf("a[%d]=%d,b[%d]=%d\n",i,a[i],i,b[i]);
	}

	return 0;
}

结果如下:

a[0]=0,b[0]=0
a[1]=1,b[1]=1
a[2]=2,b[2]=2
a[3]=3,b[3]=3
a[4]=4,b[4]=4
a[5]=5,b[5]=5
a[6]=6,b[6]=6
a[7]=7,b[7]=7
a[8]=8,b[8]=8
a[9]=9,b[9]=9

五、指针和字符串

在C语言中,大多数的字符串操作其实就是指针的操作.

5.1 通过指针修改字符串中的字符

#include<stdio.h>

int main()
{
	char s[] = "hello world";
	char *p = s; //数组名就是首元素的地址, 也就是'h'字符的地址, 所以*p取的值是'h'

	printf("*p=%c\n",*p); //取首元素的值,*p=h
	
    p[0] = 'a'; //修改首元素的值,相当于s[0]='a'

	printf("*p=%c\n",*p);// *p=a
	return 0;
}

5.2 通过指针偏来修改字符串

#include<stdio.h>

int main()
{
    char buf[100] = "hello world";
    char *p = buf; // 指向字符串首地址

    printf("*p=%c\n",*p); // *p=h

    *(p+3) = 'A';//修改当前指针后3个地址对应的值,注意此时指针没有移动, 指向的还是首地址,它的值还是'h'

    printf("*p=%c,buf=%s\n",*p,buf); // *p=h,buf=helAo world


    //移动指针
    p+=6; //+=赋值,会真正移动指针
    printf("*p=%c\n",*p); // *p=w

    *p = 'B';//修改此时指针指向地址的值
    printf("buf=%s\n",buf); // buf=helAo Borld

    return 0;
}

5.3 char *和字符串的关系

由于char * 是一级指针可以接收一个字符串变量(char 数组名) 或者字符串常量,所以按照%s输出指针变量其实是和输出数组名的效果一样的, 都是字符串内容. 如下所示:

char a[] = "hello"; //定义一个字符串
char *c = a; //一级指针指向数组名,可以当做数组使用
printf("c=%s,a=%s\n",c,a);  //结果都是hello字符串

字符串常量和字符串变量定义和区别
1、字符串常量
定义:在一个双引号""内的字符序列或者转义字符序列称为字符串常量
例如:“HA HA!” “abc” “\n\t”
这些字符串常量是不能改变的,如果试图改变指针所指向的内容是错误的,因为字符串常量是存在静态内存区的,不可以改变的。
如定义字符串常量:

char* a="i love you.";
*a='h';  //试图改变它

这是错误的。系统显示:

windows上报错如下:
string.exe 中的 0x00d71398 处未处理的异常: 0xC0000005: 写入位置 0x00d7573c 时发生访问冲突或者报该内存不能为written。

在gcc环境下报错如下:
Process finished with exit code 138 (interrupted by signal 10: SIGBUS)

2、字符串变量
在C语言中没有纯粹的c语言字符串变量,可以通过一个字符数组来体现,这样就可以对字符数组中的内容进行改变!
如定义字符串变量:

char a[]="i love you.";
*a='h'; //将数组首地址对应的元素的值修该为h

5.4 char ** 和char*[]以及字符串的关系

char ** 是二级指针,用于接收一级指针的地址, 而char *[]是一级指针数组, 数组名就是首元素(一级指针)的地址,所以可以被char ** 接收, 那如果char ** 指向数组名,那么就可以当做数组使用. 所以就有下面的关系:

char a[] ="hello";//定义一个字符串变量,字符串是特殊的字符数组
char *s[2]; //定义一个一级指针数组,元素都是一级指针
s[0]  = a;//一级指针指向字符串数组名,数组名就是地址,可以当做a来使用, 也就是char *可以当做a使用
char **p = s; //二级指针指向一级指针数组名, 可以当做s来使用

printf("p[0]=%p,s[0]=%p,a=%p,a[0]=%p\n",p[0],s[0],a,&a[0]); //结果都是同一个地址, p[0]的类型是char *,所以*p取出的也是char *类型
printf("p[0]=%s,s[0]=%s,a=%s,a[0]=%c\n",p[0],s[0],a,a[0]);//结果都是字符串内容hello, 除了a[0] = 'h'

printf("*p=%p,**p=%c\n",*p, **p);//*p的结果类型是char * ,而**p的结果类型是char

结果如下:

p[0]=0x16b4bb5fc,s[0]=0x16b4bb5fc,a=0x16b4bb5fc,a[0]=0x16b4bb5fc
p[0]=hello,s[0]=hello,a=hello,a[0]=h
*p=0x16b4bb5fc,**p=h

总结: char** 指向char*的数组, 所以char**的指针变量可以当做char*数组用, 而char* 指向char数组,所以char *可以当做char数组用, 因此char **的指针变量可以直接操作char数组的元素.

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-29 22:54:07  更:2022-01-29 22:54:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:00:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码