C语言学习第九天

英文:
assignment of read-only variable ‘a’:说明a被const修饰了,不可修改
string:字符串
cmp=compare:比较
cpy=copy:拷贝

回顾:
1.C语言变量的四种类型
局部非静态变量
使用范围
定义到最近花括号
内存生命周期
定义到最近花括号
局部静态变量
使用范围
定义到最近花括号
内存生命周期
定义到最近花括号
全局非静态变量
使用范围
定义到后续函数:同一个文件
声明到后续函数:不同文件
内存生命周期:
程序启动到程序结束
全局静态变量
使用范围:仅限于当前文件到后续函数
内存生命周期:
程序启动到程序结束

2.static关键字的特点
修饰的变量和函数仅限于当前文件,在某些场合能够降低乱序概率

3.指针(C的灵魂)
3.1.指针变量概念:本质就是一个变量,只能存地址(32位,4字节/64位,8字节)
俗称指向一块内存区域
利用指针变量可以对指向的内存进行读查看,写修改
3.2.指针变量定义语法格式:
int * p或者int* p或者int *p;
int *p1, *p2, …, *pn;
3.3.指针变量的初始化
int a = 250;
int p = &a;
立刻浮现一个内存的分布图!
或者
p = &a; //p指向a
int b = 520;
p = &b; //p由指向a变成执行b
3.4.通过指针变量来访问指向的内存区域:&和

*p; //读查看
*p = 新值; //写修改
3.5.两个特殊指针:空指针NULL和野指针
空指针保存的地址是0
野指针保存的地址是随机地址,不要有野指针,如果有一定要初始化为空指针
空指针和野指针的编程技巧
int *p; //野指针
//让野指针变空指针
int *p = NULL; //安全

//一定注意:每次使用指针时,要判断指针是否为空指针
if(NULL == p)
不合法的地址

else
合法的地址

或者
if§
合法的地址

else
不合法的地址

或者
if(!p)
不合法的地址

else
合法的地址

3.6.指针的计算
前提是指针的计算基于数据类型
char *p = NULL; p++;
short *p = NULL; p++;
int *p = NULL; p++;

3.7.指针和数组的那点事儿(之前的知识都是指针和变量的关系)
int a[] = {1,2,3,4,5}; //a是数组的首地址
int *p = a; //p保存数组的首地址,p指向数组a
脑子立马浮现内存分布图
[]:两步运算:先求地址后取值,a[3]: a+3, *(a+3)
int *p1 = &a[1];
int p2 = &a[4];
int n = p2 - p1 = 3; //实际地址空间差:3
4=12字节
结论:指针相减,得到的是相差元素个数!
通过指针变量p来读查看和写修改数组的方式:
for(int i = 0; i < len; i++)
printf("%d %d %d %d\n", a[i], *(a+i), p[i], *(p+i));
for(int i = 0; i < len; i++) {
a[i] *= 10;
p[i] *= 10;
*(a+i) *= 10;
*(p+i) *= 10;
}
for(p = a; p < a + len; p++)
printf("%d %d %d %d\n", a[i], *(a+i), p[i], *(p+i));
for(p = a; p < a + len; )
printf("%d %d %d %d\n", p++); //先算p,后p++


3.7.常量,常量指针,指针常量,常量指针常量:围绕关键const(笔试题必考)
a)常量定义:不可修改的值,例如:250,'A’等
b)const关键字功能:常量化,四种形式:
1.const可以修饰普通变量,一旦修饰该变量就会被当成常量处理
即其值一经初始化再也不能改
例如:
const int a = 250;
a = 200; //gcc编译时会报错

2.常量指针:不能通过指针变量来修改指向的内存区域的值(保护内存区域不可乱改)
例如:
int a = 250;
const int *p = &a; //定义初始化一个常量指针
或者
int const *p = &a; //定义初始化一个常量指针

*p = 200; //gcc编译时会报错
printf("%d\n", *p); //可以,仅仅是读查看

int b = 300;
p = &b; //可以,指针变量p本身是可以修改的,p此时指向b
*p = 400; //gcc编译时会报错
printf("%d\n", p);//可以,仅仅是读查看
3.指针常量:指针永远指向一块内存区域,不能再指向别的地方(保护指针不可乱指向)
例如:
int a = 100;
int
const p = &a;
*p = 300; //可以,可以修改指向的内存区域
int b = 200;
p = &b; //不可以,gcc报错

4.const int * const p:常量指针常量,表示p本身不可修改,同时p指向的目标也不能修改
只能通过p来查看内存区域的值
例如:
int a = 100;
const int* const p = &a;
*p = 300; //不可以,可以修改指向的内存区域
int b = 200;
p = &b; //不可以,gcc报在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

printf("%d\n", &p); //可以

3.8.无数据类型指针:void *
a)无数据类型指针(void *)概念:它也是一个指针变量,也保存一个地址,同样占4字节内存空间
只是它指向的内存区域的数据类型是不可知的
称这种指针为无数据类型指针,写法:void *
例如:
void *p = &a;

b)无类型指针特点:
1.通过此种指针变量是无法获知指向的内存区域保存的数据的数据类型
2.不能直接对无类型指针进行解引用操作"*",因为你不知道指向的内存区域的数据类型
也就不知道将来要取几个字节的数据,如果要想通过无类型指针获取内存的数据
必须做数据类型的转换(为了代码的可读性建议采用强制转换)
3.无类型指针void *加减几,实际的地址就是加减几
void *p = 0;
p++; //p=1
p++; //p=2
4.所有情况的案例代码
例如:
int a = 0x12345678;
void *p = &a; //虽然p指向a,但是通过p无法获取a变量的数据类型
*p = 300; //gcc编译报错,不清楚到底取几个自己的数据,没办法只能给你报错

  问:如何通过一个指针来获取a的数据呢?
  答:
  方式1:
         int a = 0x12345678;
         int *p = &a;
         printf("%#x\n", *p);
         虽然能够一次性获取4字节数据,但是现在不想一次性获取4个字节数据,想获取
         其中1个字节数据或者2字节数据或者4字节数据,显然这种方式做不到!
         要想实现这个功能,只需做以下方式的代码:
   方式2:通过有类型的指针变量来获取1字节,2字节
        获取1字节:
         int a = 0x12345678;
        //char *p = &a;  //隐式转换,代码可读性不高,gcc还要给个警告
        char *p = (char *)&a; //强制转换,提高代码的可读性,将&a的int类型转换成char*
        printf("%#x\n", *p++); //0x78
        printf("%#x\n", *p++); //0x56
        printf("%#x\n", *p++); //0x34
        printf("%#x\n", *p++); //0x12
         
        获取2字节:
         int a = 0x12345678;
        //short *p = &a;  //隐式转换,代码可读性不高 gcc还要给个警告
        short *p = ( short *)&a; //强制转换,将&a的int类型转换成short*
        printf("%#x\n", *p++); //0x5678
        printf("%#x\n", *p++); //0x1234
        
        结论:此方法虽然满足要求,但是如果做隐式转换,gcc老给一个警告
     问:能否将警告去掉呢?还有能够去掉指针变量++时跟类型相关问题呢?
     答:通过无类型指针void *实现
  方法3:通过无类型指针void *来获取1字节,2字节,4字节数据
   	获取1字节:   
int a = 0x12345678;
  	  void *p = &a; //定义一个无类型指针指向a,无需强转,关键是gcc不给警告
  char *p1 = (char *)p; //将无类型指针强转为char*
               printf("%#x\n", *p1++); //0x78
               printf("%#x\n", *p1++); //0x56
               printf("%#x\n", *p1++); //0x34
               printf("%#x\n", *p1++); //0x12
               或者:
               printf("%#x\n", *(char *)(p+0)); //0x78,先将p墙砖为char *指针,然后取值
               printf("%#x\n", *(char *)(p+1)); //0x56
  printf("%#x\n", *(char *)(p+2)); //0x34
	  printf("%#x\n", *(char *)(p+3)); //0x12
        获取1字节:   
int a = 0x12345678;
  	  void *p = &a; //定义一个无类型指针指向a,无需强转,关键是gcc不给警告
  short *p1 = (short *)p; //将无类型指针强转为char*
               printf("%#x\n", *p1++); //0x5678
               printf("%#x\n", *p1++); //0x1234
               或者:
               printf("%#x\n", *(char *)(p+0)); //0x5678
               printf("%#x\n", *(char *)(p+2)); //0x1234

3.9.指针和函数的那点事儿(目前掌握指针和变量的那点事儿,指针和数组的那点事儿)
a)指针作为函数的形参
结果:函数通过指针能够访问操作指向的内存
参考代码:swap.c(实现两个数交换和设置某个位为1和清0)
例如:
void A(int *px, int *py, int *pm, int *pn)
{
//读查看
printf("%d %d %d %d\n", *px, *py, *pm, *pn);
//写修改
*px = …
*py = …
*pm = …
*pn = …
}

b)指针作为函数的返回值
结果:此类函数又称指针函数,也就是函数的返回值是一个指针,将来函数返回的是一个地址
跟返回的变量的内存生命周期相关,跟变量的使用范围无关
切记:千万要注意别返回一个野指针(笔试必考,结合四大类型的变量)
语法格式:返回值数据类型 *函数名(形参表){函数体语句}
例如:

//定义指针函数
int g_a = 250; //定义全局非静态变量
static int g_b = 520; //定义全局静态变量
int *A(void)
{
int a = 200; //定义局部非静态变量
static int b = 100; //定义局部静态变量
return &a; //返回一个野指针,因为变量a的内存随着函数的返回而消失
或者
return &b;  //可以,因为此内存一次分配终身使用
或者
return &g_a; //可以,因为此内存一次分配终身使用
或者 
return &g_b; //可以,因为此内存一次分配终身使用
}

int main(void)
{
int *p = A();
*p = 30000; //修改变量的值
printf("%d\n", *p); //查看指向的内存的数据
return 0;
}

c)函数,指针,数组
函数访问数组的公式:
void A(int a[], int len); //其中a是数组的地址
等价于
void A(int *p, int len);


4.C语言字符串相关内容
4.1.回顾字符常量:用单引号包含,例如:‘A’,‘B’,'1’等
实际内存存储的是对应的整型数,ASCII码
占位符:%c

4.2.字符串定义:由一组连续的字符组成,并且用""包含起来,并且最后一个字符必须是’\0’
此’\0’表示字符串的结束,此’\0’的ASCII码是0
'0’的ASCII为48
注意:研究字符串最终研究的就是里面的一个一个的字符
例如:“abcefg\0”

4.3.字符串特点
a)字符串的占位符:%s
printf("%s\n", “abc\0”); //直接跟字符串
或者
printf("%s\n", 字符串的首地址);
b)字符串占用的内存空间是连续的,并且每个字节存储一个字符
参见字符串内存图.png
注意:’\0’字符串的结束符如果后面还有内容,那么这些内容为无效的
例如:“abc\0efg\0”
printf("%s\n", “abc\0efg”); //abc
c)多个并列的字符串将来会由gcc帮你合并成一个字符串
“abc”“efg"合并成"abcefg”
printf(“abc”“efg\n”); //abcefg

4.4.字符串和指针的那点事儿
a)定义一个字符指针变量并且指向一个字符串,本质是指向这个字符串的首地址
也就是指向字符串中第0个字符的首地址(也就是字符a的首地址)
定义并且初始化字符串指针变量形式:
char *p = “abcefg”;
b)如果让一个字符指针变量指向一个字符串,此字符串无需跟’\0’,gcc将来帮你添加’\0’
d)切记:不能通过字符指针变量来修改字符串的值,只能查看(笔试题必考)
//读查看
printf("%s\n", p);
*(p + 2) = ‘C’; //目标将第2个字符c变成C,做不到,报错

4.5.字符串和数组的那点事儿,两种写法:
a)写法1:
char a[] = {‘a’, ‘b’, ‘c’, ‘\0’};
注意:如果想把a当成字符串,需要手动最后添加’\0’,如果不添加a仅仅就是一个包含三个元素
的数组,而不是有效字符串
b)写法2:
char a[] = “abc”;
注意:无需添加’\0’,gcc编译器将来帮你追加一个’\0’

c)切记:不管是哪种写法,字符数组中的元素是可以修改的(笔试题必考)
例如:将第2元素’c’修改为’C’:
a[2] = ‘C’;
或者
*(a + 2) = ‘C’;

4.6.笔试题必考题目:编写一个字符串比较函数my_strcmp
参考代码:strcmp.c

4.7.标准C语言提供的字符串操作函数(都是大神写好的,咱直接调用即可)
坚定信念:自己实现一个字符串操作函数完全没问题!
如果要用以下大神写好的字符串操作函数,需要添加头文件:#include <string.h>

a)strlen函数:功能获取字符串有效长度(不包括’\0’)
例如:printf(“长度:%d\n”, strlen(“abc”)); //3
或者
char *p = “abc”;
printf(“长度:%d\n”, strlen§); //3

b)strcat函数:功能是字符串拼接
例如:
char a[10] = “abc”;
char *p = NULL;
p = strcat(a, “xyz”); //把xyz拼接在abc的后面保存到数组中
printf("%s %s\n", p, a); //abcxyz abcxyz

c)strcmp函数:功能是字符比较函数
例如:
char *p1 = “abc”;
char *p2 = “xyz”;
int ret = strcmp(p1, p2);
int ret = strcmp(“abc”, “xyz”); //本质最终传递的是字符串的首地址

d)strcpy:字符串拷贝函数,会覆盖原先的字符串
例如:
char a[10] = “abc”;
char *p = NULL;
p = strcpy(a, “xyzmn”);
printf("%s %s\n", p, a);

e)sprintf(游哥最爱):功能把数字(250)转成字符串"250"保存到数组中
例如:
char a[50] = {0};
sprintf(a, “%d %g, %c”, 250, 250.2, ‘A’);
printf("%s", a);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值