概述
嵌入式C语言——学习笔记
- 计算机程序语言的学习思路?
- GCC的使用及其常用选项介绍
- gcc概述
- C语言编译过程
- C语言常见的错误
- 预处理的使用
- 宏展开下的 #、##
- C语言常用关键字及运算符操作
- 关键字
- sizeof、return
- 数据类型
- char
- int
- unsigned、signed
- float、double
- 自定义数据类型
- struct
- union
- enum
- typedef
- 逻辑结构
- if、else
- switch、case、default
- do、while、for
- 类型修饰符
- 运算符
- 算数运算
- 逻辑运算
- 位运算
- 赋值运算
- 内存访问符号
- 内存空间
- 指针概述
- 指针+修饰符
- 指针+运算符
- ++、--、+、-
- [ ]
- 逻辑操作符
- 多级指针
- 数组
- 数组的定义及初始化
- 空间的赋值
- strcpy , strncpy
- 非字符空间
- 指针数组
- 多维数组
- 结构体
- 字节对齐
- 内存分布
- 函数
- 函数概述
- 输入参数
- 返回值
- 常见面试题
计算机程序语言的学习思路?
基本程序设计思想 + 语言工具的特性
基本程序设计思想:
- 数据类型、运算符、条件分支、循环设计
- 面向对象的设计
C语言工具的特性:
- 比如操作底层,尤其是内存地址的寻址及操作,指针的使用
- 掌握C语言的设计思路,比普通的语法要重要的多
- 万变不离其宗,掌握C语言的核心规律。
学习过程主要就是弄清楚三个问题:
- 什么时候用?(在什么情况下会去使用)
- 怎么用?
- 为什么要这样设计?(通过这个语法所要传达的意义)
GCC的使用及其常用选项介绍
掌握C语言如何变成机器指令的过程
gcc工具的几个常用选项的意义
如果你对gcc越熟悉或者理解更深入,对后面学驱动和arm体系架构都有一定的帮助
gcc概述
GCC(GNU Compiler Collection,GNU编译器套件)是由GNU开发的编程语言编译器。其主要的目的就是将我们能理解的一套语言翻译成一个机器所能理解的语言,相当于翻译官。
查看gcc版本
gcc -v
gcc编译
gcc -o 输出文件名 输入文件名
注:“-o 输出文件名” 相当于一个整体,中间不能有其他东西
运行输出文件
./输出文件名
C语言编译过程
查看编译过程
gcc -v -o 输出文件名 输入文件名
- 预处理(将 .c 文件转换为 .i 文件)
gcc -E -o a.i test.c
注:define include不是关键值,它们经过预处理就没有了,替换到相对应的位置。
- 编译(将 .i 文件转换为 .s 文件)
.s结尾文件:
小写的 s文件,在后期阶段不会再进行预处理操作了,所以我们不能在其内写上预处理语句。
一般是 .c 文件经过汇编器处理后的输出【即 gcc -S 包括 “ 预处理 + 编译 ” 】。 如 GCC 编译器就可以指定 -S 选项进行输出,且是经过预处理器处理后的了。
例如:gcc -S -o a.s test.c-----生成.s结尾的文件,打开为汇编代码
- 汇编(将 .s 文件转换为 .o 文件)
.o结尾文件:
只编译不链接形成.o文件。里面包含了对各个函数的入口标记,描述,当程序要执行时还需要链接(link).链接就是把多个.o文件链成一个可执行文件。如 GCC 编译器就可以指定 -c选项进行输出。打开是乱码(二进制文件)。
例如:gcc -c -o a.o a.s-------结果生成.o文件,打开为能被cpu直接识别的二进制代码
- 链接
gcc -o xxx test.c
C语言常见的错误
预处理错误:
#include “name” //自定义的头文件
#include //系统库的头文件
not find 没有找到文件
使用【gcc -I对应头文件的目录 -o build test.c】指定头文件的目录
编译错误:
语法错误,如:缺少 ;{ }
链接错误:
- 原材料不够:undefined reference to ‘xxx’
寻址标签是否实现,链接时是否加入一起链接
如果有多个.c文件可以先将他们编译成.o文件,在一起进行链接gcc -c -o a.o 001.c
gcc -c -o b.o 002.c
gcc -o build a.o b.o
- 原材料多了:multiple definition of ‘xxx’
多次实现了标签,只保留一个标签实现
预处理的使用
- #include 包含头文件
- #define 宏 主要用于替换,不进行语法检查
普通宏:#define 宏名 宏体
如:#define ABC (5+3)
尽量加括号,防止产生歧义,如printf(“the %dn”, ABC*5);函数宏:#define 函数宏(x) 函数宏体
如:#define ABC (x) (5+(x))
同样也是要加上括号
- #ifdef #else #endif 用来进行调试版本和发行版本的切换
#include <stdio.h>
int main()
{
#ifdef ABC
printf("======%s======n",__FILE__);
#endif
printf("hello world!n");
return 0;
}
想运行 printf (“======%s======n”, __FILE__);
方法一:在main函数前添加 " #define ABC " (但不太建议使用这种方法,以为还要修改原文件)
方法二:在编译时使用 【gcc -D】通过编译器认为的添加宏名,,使用 gcc -DABC 相当于 #define ABC (如:gcc -DABC -o build test.c)
- 预定义宏
系统已经定义好的,我们都可以直接使用,在调试中用得比较多
/*
__FUNCTION__ :函数名
__LINE__ :行号
__FILE__ :文件名
*/
#include <stdio.h>
int fun()
{
printf("the %s, %s, %d", __FUNCTION__, __FILE__, __LINE__);
return 0;
}
int main()
{
fun();
return 0;
}
//返回结果:fun, test.c, 4
宏展开下的 #、##
#: 字符串化
##:连接符号
#include <stdio.h>
#define ABC(x) #x //这比较简单,就是将 x 转换为字符串型的
#define DAY(x) myday##x
int main()
{
int myday1 = 10;
int myday2 = 20;
printf(ABC(abn));
printf("the day is %dn", DAY(1));
return 0;
}
//输出结果:
ab
the day is 10
常见的使用:
C语言常用关键字及运算符操作
掌握C语言 的常用关键字及其应用场景,使用技巧
掌握位运算的典型操作
掌握常用的逻辑操作
when to do? how to do? why to do?
关键字
关键字:编译器 预先定义了一定意义的 字符串(共32 个字符串)
auto :声明自动变量
break:跳出当前循环
case:开关语句分支
char :声明字符型变量或函数返回值类型
const :声明只读变量
continue:结束当前循环,开始下一轮循环
default:开关语句中的“默认”分支
do :循环语句的循环体
double :声明双精度浮点型变量或函数返回值类型
else :条件语句否定分支(与 if 连用)
enum :声明枚举类型
extern:声明变量或函数是在其它文件或本文件的其他位置定义
float:声明浮点型变量或函数返回值类型
for:一种循环语句
goto:无条件跳转语句
if:条件语句
int: 声明整型变量或函数
long :声明长整型变量或函数返回值类型
register:声明寄存器变量
return :子程序返回语句(可以带参数,也可不带参数)
short :声明短整型变量或函数
signed:声明有符号类型变量或函数
sizeof:计算数据类型或变量长度(即所占字节数)
static :声明静态变量
struct:声明结构体类型
switch :用于开关语句
typedef:用以给数据类型取别名
unsigned:声明无符号类型变量或函数
union:声明共用体类型
void :声明函数无返回值或无参数,声明无类型指针
volatile:说明变量在程序执行中可被隐含地改变
while :循环语句的循环条件
sizeof、return
sizeof:编译器给我们查看内存空间容量的一个工具,用来计算数据类型或变量长度(即所占字节数)
return:返回的概念
#include <stdio.h>
int main()
{
int a;
printf("the a is %lun", sizeof(a));
}
sizeof:大小一般由编译器来决定
数据类型
C语言操作对象:资源/内存(内存类型的资源,LCD缓存、LED灯)
C语言如何描述这些资源的属性?
大小:使用数据类型限制内存的大小
long在linux64环境下所占用字节位8,也就间接说明了long在macOS下的字节长度也是8。(这是因为MacOS系统和Linux都是类Unix系统,只不过基于不同的内核)
char
硬件芯片操作的最小单位:bit
软件操作的最小单位:8bit == 1B (即char)
int
大小:int 没有固定的大小,根据编译器来决定
编辑器最优的处理大小:系统一个周期,所能接收的最大处理单位,int
32bit系统 4B int
16bit系统 2B int
进制问题:
int a = 10; 表示为10进制
int a = 010; 表示为8进制
int a = 0x10; 表示16进制
unsigned、signed
区别:内存空间的最高字节 是符号位, 还是数据
无符号:数据
有符号:数字
float、double
float 4B
double 8B
1.0 1.1 为double类型
1.0f 1.1f 为float类型
自定义数据类型
C编译器默认定义的内存分配不符合实际资源的形式
自定义 = 基本元素的组合
struct
struct 相当于累加的过程
struct myabc{
unsigned int a;
unsigned char b;
unsigned int c;
};//此处还未申请内存空间
struct myabc mybuf;//申请空间
顺序有要求的:顺序的不同也会影响到最后大小的不同
union
union 相当于叠加的过程
共用体 就是 大家共用一个起始地址来申请空间
共用体 data 中,成员 f 占用的内存最多,为 8 个字节,所以 data 类型的变量(也就是 a、b、c)也占用 8 个字节的内存,请看下面的演示:
#include <stdio.h>
union data{
int n;
char ch;
short m;
};
int main(){
union data a;
printf("%d, %dn", sizeof(a), sizeof(union data) );
a.n = 0x40;
printf("%X, %c, %hXn", a.n, a.ch, a.m);
a.ch = '9';
printf("%X, %c, %hXn", a.n, a.ch, a.m);
a.m = 0x2059;
printf("%X, %c, %hXn", a.n, a.ch, a.m);
a.n = 0x3E25AD54;
printf("%X, %c, %hXn", a.n, a.ch, a.m);
return 0;
}
运行结果:
4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54
这段代码不但验证了共用体的长度,还说明共用体成员之间会相互影响,修改一个成员的值会影响其他成员。
enum
枚举和宏其实非常类似:宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏。
例如,列出一个星期有几天:
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
可以看到,我们仅仅给出了名字,却没有给出名字对应的值,这是因为枚举值默认从 0 开始,往后逐个加 1(递增);也就是说,week 中的 Mon、Tues … Sun 对应的值分别为 0、1 … 6。
我们也可以给每个名字都指定一个值:
enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };
更为简单的方法是只给第一个名字指定值:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
这样枚举值就从 1 开始递增,跟上面的写法是等效的。
typedef
typedef:数据类型的别名
来替代系统默认的基本类型名称、数组类型名称、指针类型名称与用户自定义的结构型名称、共用型名称、枚举型名称等。
一般看到 xxx_t:就是typdef定义过的
- 为基本数据类型定义新的类型名
typedef unsigned int uint32_t;
- 为自定义数据类型(结构体、共用体和枚举类型)定义简洁的类型名称
typedef struct tagPoint
{
double x;
double y;
double z;
} Point;
- 为数组定义简洁的类型名称
typedef int INT_ARRAY_100[100];
INT_ARRAY_100 arr;
- 为指针定义简洁的名称
typedef char* PCHAR;
PCHAR pa;
逻辑结构
if、else
条件
if(条件表达式)
xxx;
else
yyy;
switch、case、default
多分支
switch(整型数字){
case 1:
aaa;
break;
case 2:
bbb;
break;
default:
ccc;
}
do、while、for
循环
for:次数
while:条件
continue、break、goto
continue: 语句的作用是跳过循环体中剩余的语句而强制进入下一次循环。continue语句只用在 while、for 循环中,常与 if 条件语句一起使用,判断条件是否成立。
break:用于 while、for 循环时,会终止循环而执行整个循环语句后面的代码。break 关键字通常和 if 语句一起使用,即满足条件时便跳出循环。
类型修饰符
auto、register、static、extern、const、volatile
对内存资源存放位置的限定
auto:为默认情况,分配的内存可读可写的区域,区域如果在{ }中,则为站空间
register:限定变量定义在寄存器是的修饰符,它可以定义一些要快速访问的变量。编译器会尽量的安排CPU的寄存器去存放这个变量,如果寄存器不足时,变量还是存放在存储器中。(寄存器里的变量是没有地址的,所以register修饰的变量使用 & 去取地址是不起作用的)
static:
隐藏与隔离的作用
全局变量虽然属于静态存储方式,但并不是静态变量。全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,全局变量在各个源文件中都是有效的。
如果我们希望全局变量仅限于在本源文件中使用,在其他源文件中不能引用,也就是说限制其作用域只在定义该变量的源文件内有效,而在同一源程序的其他源文件中不能使用。这时,就可以通过在全局变量之前加上关键字 static 来实现,使全局变量被定义成为一个静态全局变量。这样就可以避免在其他源文件中引起的错误。也就起到了对其他源文件进行隐藏与隔离错误的作用,有利于模块化程序设计。
保持变量内容的持久性
有时候,我们希望函数中局部变量的值在函数调用结束之后不会消失,而仍然保留其原值。即它所占用的存储单元不释放,在下一次调用该函数时,其局部变量的值仍然存在,也就是上一次函数调用结束时的值。这时候,我们就应该将该局部变量用关键字 static 声明为“静态局部变量”。
static 是用来修饰变量和函数的:
- 修饰局部变量-称为静态局部变量
- 修饰全局变量-称为静态全局变量
- 修饰函数-称为静态函数
全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用 extern 关键字再次声明这个全局变量。
静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。
静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
extern:如果一个工程由多个源文件组成,在一个源文件中想引用另一个源文件中已定义的 外部变量,就需要在引用变量的源文件中用extern关键字声明变量。
extern和static关系:
extern和static两者之间是有一种相克的关系:用了extern的不能用static,用了static的不能用extern,extern是声明让别的文件中能够使用,static修饰之后的函数或全局变量不能被其他文件使用。
const:
(1)const是一个C语言的关键字,它的作用是限定一个变量不允许被改变。
(2)const是给系统看,让系统不要改变我的值。
(3)const也是给程序员看,让程序员看这里为什么要用const,让程序员不要轻易修改,其实可以无视const,用指针调用指针来把const的作用给无视掉。
(1)修饰局部变量
const int n=5;
int const n=5;
这两种写法是一样的,都是表示变量n的值不能被改变了,需要注意的是,用const修饰变量时,一定要给变脸初始化,否则之后就不能再进行赋值了。
接下来看看const用于修饰常量静态字符串,例如:
const char* str="fdsafdsa";
如果没有const的修饰,我们可能会在后面有意无意的写str[4]=’x’这样的语句,这样会导致对只读内存区域的赋值,然后程序会立刻异常终止。有了const,这个错误就能在程序被编译的时候就立即检查出来,这就是const的好处。让逻辑错误在编译期被发现。
(2)常量指针与指针常量
常量指针是指针指向的内容是常量,可以有一下两种定义方式。
const int * n;
int const * n;
需要注意的是一下两点:
1、常量指针说的是不能通过这个指针改变变量的值,但是还是可以通过其他的引用来改变变量的值的。
int a=5;
const int* n=&a;
a=6;
2、常量指针指向的值不能改变,但是这并不是意味着指针本身不能改变,常量指针可以指向其他的地址。
int a=5;
int b=6;
const int* n=&a;
n=&b;
指针常量是指指针本身是个常量,不能在指向其他的地址,写法如下:
int *const n;
需要注意的是,指针常量指向的地址不能改变,但是地址中保存的数值是可以改变的,可以通过其他指向改地址的指针来修改。
int a=5;
int b=6;
int* const p=&a;
*p=8;
p = &b; //错误,指向的地址不能改变
区分常量指针和指针常量的关键就在于星号的位置,我们以星号为分界线,如果const在星号的左边,则为常量指针,如果const在星号的右边则为指针常量。如果我们将星号读作‘指针’,将const读作‘常量’的话,内容正好符合。int const * n;是常量指针,int *const n;是指针常量。
指向常量的常指针
是以上两种的结合,指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过其他的普通指针改变变量的值。
const int* const p;
volatile:告知编译器编译方法的关键字,不进行优化编译。由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。(如 for 循环的 i++ 可能会在寄存器里进行,不会将 i++ 的结果写入到RAM中后,CPU再进行读取)
运算符
算数运算
【 +、-、*、/、% 】
*、/ :CPU一般不支持乘、除,CPU可能要多个周期,甚至要利用软件的模拟方法去实现乘法
% :求模
用法:
(1) 取一个范围的数
n % m = res,可以获得 0 ~ (m - 1) 范围的数
(2) 得到 M 进制的一个个位数
(3) 得到循环数据结构的下标
逻辑运算
【 || 、&&、 >、>=、 <、 <=、!、? : 】
返回结果就是:1 、0 (非0即为真)
|| 、&&:
A || B 不等价为 B || A (一个为真即为真)
A && B (全部为真才为真)
位运算
【 <<、>>、&、|、^、~ 】
<<、>>:
左移:乘法 2 二进制下的移位(m<<n ===> m2^n)
[数据、数字(符号)]
右移:
与符号变量有关
&、|、^ :
&:屏蔽(A & 0 ----> 0)、取出(A & 1 ----> A),清零器
int a = 0x1234;
a & 0xFF00; //屏蔽第8位,取出高8位
|:保留( A | 0 ----> A)、设置高电平( A | 1 ----> 1),设置器
应用:
//设置一个资源的bit5(从bit0开始)为高电平,其他位不变
int a;
a = a | (0x1<<5); // ====> a|(0x1<<n)
//清除bit5
a = a & 0x011111 //这是错误的,如果在32位的单片机中,0前面还有26个'0',相与之后高26位也被清除
a = a & ~(0x1<<5); // ====> a & ~(0x1<<n)
^ :异或(相同为假,不同为真)
主要用于加密算法中:如,AES、SHA1…
//交换两个数(不引入第三方变量)
int fun()
{
int a = 20;
int b = 30;
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
~:每一位都要取反
赋值运算
【 =、+=、-=、&=、…… 】
内存访问符号
【 ()、[]、{}、->、. 、&、*】
():1.限制符、2.函数访问
内存空间
指针概述
指针变量:存放指针的盒子
C语言编译器对指针这个特殊的概念,要解决2个问题:
1、分配一个盒子,盒子的大小是多少?
在32bit系统中,指针就是4个字节
#include <stdio.h>
int main()
{
int *p1;
char *p2;
printf("the p1 is %u, the p2 is %un",sizeof(p1),sizeof(p2));
}
//输出结果:
//the p1 is 4, the p2 is 4
2、盒子里存放的地址所指向内存的读取方法是什么?
char *p; 表示一次读取一个字节
int *p; 表示一次读取4个字节
#include <stdio.h>
int main()
{
int a = 0x12345678;
char *p;
p = &a;
printf("the p is %xn",*p1);
}
//输出结果:
//the p is 78
指针+修饰符
const:
const char *p;
char const *p;
常量指针,指针的指向可以变,但指针指向的内容不能变。===> 字符串 “hello world”
char * const p;
char *p const;
指针常量,指正的指向不能变,但指针指向的内容能变。===> 硬件资源,如,LCD、显卡缓存
const char* const p;
希望存放在 ROM 空间中
#include <stdio.h>
int main()
{
char *p1 = "hello world!n";
printf("the one is %xn",*p1);
*p1 = 'a';
printf("the %sn",p1);
}
//输出结果:
//the one is 68
//Segmentation fault(段错误)
" "修饰的就是字符串,即为const char*,只读常量。
#include <stdio.h>
int main()
{
char buf[] = {"hello world!n"};
char *p2 = buf;
printf("the one is %xn",*p2);
*p2 = 'a';
printf("the %sn",p2);
}
//输出结果:
//the one is 68
//the aello world!
char*和char[]区别
本质上来说,char *s定义了一个char型的指针,它只知道所指向的内存单元,并不知道这个内存单元有多大,所以:
当char *s = “hello”;后,不能使用s[0]=‘a’;语句进行赋值。这是将提示内存只能读,不能为写。
当用char s[]=“hello”;后,完全可以使用s[0]=‘a’;进行赋值,这是常规的数组操作。
#include <stdio.h>
int main(int argc, char* argv[]) {
char* buf1 = "this is a test";
char buf2[] = "this is a test";
printf("size of buf1: %dn", sizeof(buf1));
printf("size of buf2: %dn", sizeof(buf2));
return 0;
}
//输出结果:
//size of buf1: 4
//size of buf2: 15
(1)buf1 指向的 “this is a test” 位于字符串常量区。正是指向常量区的原因buf1[0]= ‘a’;这样的操作会报错,常量区内容不可修改。
如果定义
char* a = “123”;
char* b = “123”;
其实 a 和 b 都指向同一块地址,如果可以通过a=‘4’ 去改变"123",那么b所指向的内容也跟着改变,所以a='4’肯定会报错。
(2)buf2 则是开辟一块空间,依次存放 ‘t’ ‘h’ ‘i’ ‘s’ ’ ’ ‘i’ ‘s’……。buf2指向数组的第零个元素,不可修改其指向,例如buf2++是会报错的。
volatile:
告知编译器编译方法的关键字,不进行优化编译。由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。(如 for 循环的 i++ 可能会在寄存器里进行,不会将 i++ 的结果写入到RAM中后,CPU再进行读取)
使用场景
1.程序使用RTOS,多线程中都会读写的全局变量需要使用volatile定义
2.中断和主函数中都要读写的全局变量,需要使用volatile定义
3.单片机的寄存器定义,当然这些变量已经有芯片厂商在库函数中完成定义。
typedef:
给变量类型起别名
char *name_t; //name_t是一个指针,指向了一个char类型的内存
typedef char *name_t; //name_t是一个指针类型的名称,指向了一个char类型的内存
name_t abc;
指针+运算符
++、–、+、-
指针的加减法运算,实际上加的是一个单位,单位的大小可以使用sizeof(p[0])
int *p = xxx; [0x12]
p+1 ————> [0x12 + 1*(sizeof(*p))]
int *p 和 char *p 的 p+1 是不一样的
p++、p-- :在查看的过程中还跟新了地址
[ ]
变量名[n]
理解为是地址内容的标签访问方式,取出标签里的内存值
#include <stdio.h>
int main(void)
{
int a = 0x12345678;
int b = 0x99991199;
int *p1 = &b;
char *p2 = (char *)&b; //先读取低地址
printf("the p1+1 is %x, %x, %xn",*(p1+1), p1[1], *p1+1);
printf("the p2+1 is %xn",p2[1]);
}
//输出结果
//the p1+1 is 12345678,12345678,9999119a
//the p2+1 is 11
指针越界访问
(被 const 修饰的变量还是有可能改变的)
#include <sdtio,h>
int main()
{
const int a = 0x12345678;
int b = 0x11223344;
int *p = &b;
p[1] = 0x100;
printf("the a is %x/n",a);
}
//输出结果
//the a is 100
逻辑操作符
主要还是使用 == 和 !=
- 跟一个特殊值比较
NULL / 0x00 :地址的无效值,结束标志 if ( p== 0x00 ) - 指针必须是同类型的比较才有意义
多级指针
**int p
存储地址的地址空间
char **p;
将没有关系的内容变得有顺序
当 p[m] == NULL ——> 结束了
#include <stdio.h>
int main(int argc, char **argv)
{
int i;
for (i = 0; i < argc; i++)
{
printf("the argv[%d] is %sn",i,argv[i]);
}
return 0;
}
#include <stdio.h>
int main(int argc, char **argv)
{
int i = 0;
//出现二级指针常见的模板
while (argv[i] != NULL)
{
printf("the argv is %sn",argv[i]);
i++;
}
return 0;
}
数组
数组的定义及初始化
定义一个空间:
- 名字
- 大小
- 读取方式
数据类型 数组名[m] :
m的作用域是在申请的时候
在使用时[ ] 里的数可以任意,但是会发生越界访问
数组名是一个常量符号,一定不要放到 = 的左边
空间的赋值
按照标签逐一处理
int a[10] ; [ 0 - 9 ]
a[0] = xx;
a[1] = yy;
程序员这样赋值,工作量比较大,能不能让编译器进行一些自动处理,帮助程序员写如上的程序
——》空间定义时,就告知编译器的初始化情况,空间的第一次赋值,初始化操作
数组空间的初始化 和 变量的初始化 本质不同,尤其在嵌入式的裸机开发中,空间的初始化往往需要库函数的辅助
char buf[10] ={'a','b','c'};
buf当成普通内存来看,没有问题
buf当成一个字符串来看,最后加上一个‘0’或‘0’ (字符转的重要属性,结尾一定有个‘0’)
char buf[10] = "abc";
buf[2] = 'e';
char *p = "abc";
p[2] = 'e';
//p指向的是一个常量,不能修改,这样会发生段错误
strcpy , strncpy
一块空间,当成字符空间,提供了一套字符拷贝函数
字符拷贝函数的原则:
- 内存空间和内存空间的逐一赋值的功能的一个封装体
- 一旦空间中出现了“0”这个特殊值,函数就即将结束
头文件:#include <string.h>
strcpy
strcpy() 函数用来复制字符串,其原型为:
*char *strcpy(char *dest, const char src);
【参数】dest 为目标字符串指针,src 为源字符串指针。
注意:src 和 dest 所指的内存区域不能重叠,且dest 必须有足够的空间放置 src 所包含的字符串(包含结束符NULL)。
【返回值】成功执行后返回目标数组指针 dest。
strcpy() 把src所指的由NULL结束的字符串复制到dest 所指的数组中,返回指向 dest 字符串的起始地址。
注意:strcpy 是依据 作为结束判断的,如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出(buffer Overflow)的错误情况,在编写程序时请特别留意,或者用strncpy()来取代。
strncpy
strncpy()用来复制字符串的前n个字符,其原型为:
char * strncpy(char *dest, const char *src, size_t n);
【参数说明】dest 为目标字符串指针,src 为源字符串指针。
strncpy()会将字符串src前n个字符拷贝到字符串dest。
如果src的前n个字节不含NULL字符,则结果不会以NULL字符结束。
如果src的长度小于n个字节,则以NULL填充dest直到复制完n个字节。
src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。
注意:src 和 dest 所指的内存区域不能重叠,且dest 必须有足够的空间放置n个字符。
【返回值】返回指向dest的指针(该指向dest的最后一个元素)
注意:但 strncpy 其行为是很诡异的(不符合我们的通常习惯)。标准规定 n 并不是 sizeof(s1),而是要复制的 char 的个数。一个最常见的问题,就是 strncpy 并不帮你保证
另外,如果 s2 的内容比较少,而 n 又比较大的话,strncpy 将会把之间的空间都用 填充。这又出现了一个效率上的问题,如下:
char buf[80];
strncpy( buf, "abcdefgh", 79 );
上面的 strncpy 会填写 79 个 char,而不仅仅是 “abcdefgh” 本身,还包含71 (79-8)个 ‘ ’ 。
strncpy 的标准用法为:(手工写上 )
strncpy(path, src, sizeof(path) - 1);
path[sizeof(path) - 1] = '