转行做嵌入式也有一段时间了,原来做c#以及一些其它的上层语言, 本想的是也就是仅仅是语法上有点不一样。但是实际使用的切身体会真的是只有自己才知道。很多方面刷新了我对c语言以及计算机结构体系的认知 ,绝对不仅仅是语法不一样那么简单。

 关于字符串传递函数引起的

一切源于给函数传递字符串变量这种 原来在其它高级语言地方写的 再常见不过的功能。

 1 void changeStr(char * ch)
 2 {
 3      *(ch + 1) = 'a';
 4 }
 5 
 6 //char * cstr = "hello";//报错
 7 char cstr[] = "hello";//这种可以
 8 //数组也是传的引用函数里面更改的是此处数组的内容这个不用多说
 9 changeStr(cstr);
10 printf(cstr);

 

当然对于这个问题的bug 你百度一下就会有很多帖子 ,基本上呢也会给你讲的大概差不多。如果非要死记硬背的话这种代码:

1 char * ch="hello";

他是以常量的形式存在于环境中的。具体的请看:

https://www.qb5200.com/article/405141.html 

https://blog.csdn.net/u013066730/article/details/84231452

https://blog.csdn.net/silently_frog/article/details/96607516

总而言之 char * ch=“hello” 然后再去更改ch里的元素 是一个陷阱,至于这个”hello” 的地址大意估计是由编译器更底层的控制的我们不用管。

 

又啰嗦一遍 内存地址和数据 

归根结底还是 指针的问题。内存地址 值 ,学了单片机后深深的认识到这 其实这就是计算机  汇编 ,还有编程的本质,本质就是内存管理 ,到处的计算机书 操作系统 或者其它方面讲的也都是这个,内存数据处理。单片机入门就是地址上的内存数据处理。c语言中处理字符串总是会遇到各种各样的问题也是因为没理解透。

 

还是返回c语言字符串的议题上来吧

c语言字符串的本质:需要一段连续地址的值 以\0结尾。处理的时候不要超出这个概念。以前在技术群里讨论也模糊的记得 有这个说法 ,上面的”hello”编译后是作为静态字符串存储的,在各处如果写的一模一样的”hello”那么他们在内存里本身就是同一个东西, “hello” 这种以引号引起来的 表示方式 它本质就代表了一个地址标识。另外一个”hello” 这种形式的写法编译器末尾会自动给带一个\0结尾 来确定字符串结束, 这个你是看不见 但他是存在的。依稀记得很久以前学Java时老师说string这个对象怎么是经过封装了的,巴拉巴拉,string本身分配了就是固定的数组了不允许再更改了巴拉巴拉,string c=”a”+”b” 实际上是创建了3个对象巴拉巴拉。c++里也看到了确实是包装过的对象类型,但是=号运算方式是按照值类型的处理方式运作的,也即原字符串数据完全复制一遍。再贴一遍字符串的代码:

1 char * str2 = "hello";

 

一些坑以及产生的原因(指针与分配确切空间):

而c语言指针不允许你指针随便指向一个未初始化的 地址,并不允许你一个指针随意指向一个地址 然后去修改 值 ,这是不被允许的 ,千言万语 归咎于此的根本原因。

 1 char ch1 = 0x65;
 2 char ch2 = 0x66;
 3 char ch3 = 0x00;
 4 
 5 char str1[] = "hello";//{ ch1, ch2, ch3 }这种写法意思是一样的
 6 char * str2 = "hello";
 7 str1[1] = 'a';
 8 //*(str1 + 1) = 'a';数组指针式操作 老套路
 9 
10 //str2[1] = 'a';//报错
11 //*(str2 + 1) = 'a';//我们想当然的指针地址++ 进行操作是不行的
12 //str2 = &ch1;//这种是可以 但是指向单个字符地址 并不能形成连续的字符串
13 
14 printf("%s--\r\n", str1);
15 printf("%s--\r\n", str2);//字符串参数给的是地址
16 printf("%c--\r\n", *str2);//注意字符参数给的是*p

 这也是为什么数组字符串 能够 更改元素 (数组的内存空间是确切的),char* (内存空间不确切?内容是放在静态区的?) ,所以不能随意更改。一切都归咎于内存管理, 使用内存时必须必须必须要固定的确切的 经过分配了的 确定的 空间。然后另一个 他们的这种指针的机制是相同的 所以 数组可以用 *p 的形式来访问。

 

1 int a = 44;

这个 a看似平平无奇 ,实际上这是一个内存空间确定的过程 , a代表了一个地址,编译过程只不过把这个过程变成了对应的汇编指令。

字符串不就是连续地址的值以\0结尾吗 ,于是乎有些大聪明就有了这种代码:

 1 char ch1 = 'a';
 2 char * str1;
 3 char * str2;
 4 char * str3;
 5 
 6 str1 = &ch1;
 7 str2 = str1 + 1;
 8 
 9 //这样更是错误,指针只能指向一个 给一个地址值
10 //char * str2 = { 'h', 'e', '\0' };
11 
12 //还想到 不是连续的指针 么 数组么 ,于是大聪明 这样
13 //又犯了迂回的错误 ,数组 里面 每一个元素都是指针  ,这其实是一个二维数组。
14 //char * str1[] 

单片机中除了有定义的外设寄存器以外 ,普通区域 谁允许你擅自通过地址+1的方式 去访问未知的区域的。而单独一个char 一个char 的定义 变量又不可能 给你分配到连续的区域 让你char*去关联上,这是由于c语言本身的机制决定的 ,所以我们就不要去专这个牛角尖了。

 

也正是由于指针跟数组的相关性 ,指针跟数组有时候 实参 形参可以互相换,比如最开始的代码 形参定义的是指针 确可传数组,定义的是数组也可以传指针 都是可以的 是等价的 ,都相安无事 ,前提是你要自己眼睛看仔细了 传进去的是一个char * 的话 如果你函数内部 进行了元素更改的话 你就挂了。

 

因为这这些缺陷种种限制, 所以微量氧里到处都是用的数组  形式的字符串,数组就是一串连续确切空间地址的指针。

 

 关于返回一个数组

当然 通过单片机函数不能返回数组 ,只可返回指针 ,返回一个指针么指向那里?函数大括号里的部分在函数运行结束就会被销毁,去哪里找 。

 1 char * getChPointer()
 2 {
 3     //如果想使此函数起作用可以把cc定义成全局的
 4     //static char cc = 'b';
 5     char cc = 'b';
 6     char * ch;
 7     ch = &cc;
 8     //其实c这种单细胞难用玩意儿,也没啥动态内存分配不动态内存分配
 9     //单片机编程中根本就没用到动态内存也就是堆,管理太复杂了。
10 
11     //到此方法体内所有的都是复制的 ,包括指针 则复制一模一样的指针
12     //每次调用方法都是独立副本运行,方法结束栈结束 所有内容销毁
13 
14     //当栈结束时返回的是复制的跟ch一模一样的指针 都指向cc所以没区别
15     return ch;
16 }
17 char * ch = getChPointer();
18 printf("dd\r\n");
19 printf("%c", *ch);

c语言 真的非常非常的低能 单细胞动物 。深深的感受到c 语言为什么仅仅只是汇编的抽象 ,真是一种单细胞思考方式 ,因为真的是在是太低级了 ,跟其他什么Java相比真的是低一个级别的。诸多限制,更偏向于一种野人式的,要求你的功力要更高才能驾驭。也正是由于这种第一个级别 所有的这些不顺手都不得不逼着你去想 底层到底是怎么运作的。