一,字符数组
-
概念:专门存放字符的数组,称为字符数组
-
初始化与元素引用
-
char s1[5] = {'a', 'b', 'c', 'd', 'e'}; // s1存放的是字符序列,非字符串
char s2[6] = {'a', 'b', 'c', 'd', 'e', '\0'}; // s2存放了一个字符串char s[6] = {"abcde"}; // 使用字符串直接初始化字符数组
char s[6] = "abcde" ; // 大括号可以省略s[0] = 'A'; // 索引第一个元素,赋值为 'A'
1,数组偏移量
int Array[6] = {10,20,30,40,50,60};
// 数组地址偏移量
// 000000000061FE04,000000000061FE04
printf("%p,%p",&Array[1],&Array[0]+1);
2,数组元素地址解引用
通过对数组元素地址解引用,可以获取地址空间里面的数据
int a = 10;
printf("%d\n",a);
printf("%p\n",&a);
// * 表示将地址里面的内容取出,我们把它称为解引用
printf("%d\n",*(&a));
//--------------------------
char Array[5] = {'j','a','c','k'};
printf("%c\n",Array[0]);
printf("%c,%c\n",*(Array+0),*(&Array[0]));
printf("%c,%c\n",*(Array+1),*(&Array[1]));
printf("%c,%c\n",*(Array+2),*(&Array[2]));
printf("%c,%c\n",*(Array+3),*(&Array[3]));
3.字符串常量
-
字符串常量在内存中的存储,实质是一个匿名数组
-
匿名数组,同样满足数组两种涵义的规定
-
示例:
printf("%s\n","hello");
printf("'h' addr:%p\n","hello");
printf("'e' addr:%p\n","hello" + 1); //地址+1
printf("'o' addr:%p value:%c\n","hello" + 4,*("hello" + 4)); //地址+1
printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组
printf("%p\n", &"abcd"); // 此处 "abcd" 代表整个数组
printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址
二,多维数组(二维数组)
概念:若数组元素类型也是数组,则该数组称为多维数组,就是一维数组的集合
int a0[3];
int a1[3];
int a[2][3];//a[0]-->a0 a[1]-->a1
int ; a[3] // 数组
int [3]; a[2] // 数组
第一种解释:
定义一个二维数组。该数组是由2个一维数组组成,分别是a[0] a[1]
每个一维数组由3个元素组成,所以二维数组有6个元素
数据类型 二维数组的名字[有多少个一维数组][每个一维数组有多少个元素]
// 代码释义:
// 1, a[2] 是数组的定义,表示该数组拥有两个元素
// 2, int [3]是元素的类型,表示该数组元素是一个具有三个元素的整型数组
第二种解释:
该数组一共有2行,每行由3个元素组成(2行3列)
数据类型 二维数组名[行][列]
所谓的行:表示这个二维数组一共有多少个一维数组
所谓的列:表示这个二维数组每个一维数组有多少个元素
初始化:
int a[2][3] = {{1,2,3}, {4,5,6}}; // 数组的元素是另一个数组
int a[2][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; // 错误,越界了
int a[2][3] = {{1,2,3}, {4,5,6,7}}; // 错误,越界了
int a[ ][3] = {{1,2,3}, {4,5,6}}; // OK,自动根据初始化列表分配数组元素个数
int a[2][3] = {{1,2,3}}; // OK,只初始化数组元素的一部分
元素引用:
// a[0] 代表第一个元素,这个元素是一个具有 3 个元素的数组:{1,2,3}
// a[1] 代表第二个元素,这个元素也是一个具有 3 个元素的数组:{4,5,6}
printf("%d", a[0][0]); // 输出第一个数组的第一个元素,即1
printf("%d", a[1][2]); // 输出第二个数组的第三个元素,即6
二维数组解引用
// 二维数组初始化字符串
char buf1[2][5] = {"jack","rose"};
printf("%s,%s\n",&buf1[0][0],buf[0]);
printf("%s,%s\n",&buf1[1][0],buf[1]);
// 取二维数组中的某个字符
printf("%c,%c,%c,%c\n",buf1[0][1],*(&buf1[0][1]),*(buf1[0]+1),*(*(buf1+0)+1));
printf("%c,%c,%c,%c\n",buf1[1][2],*(&buf1[1][2]),*(buf1[1]+2),*(*(buf1+1)+2));
数组地址偏移
#include <stdio.h>
int main()
{
char Names[3][5] = {"jack","rose","ken"};
// printf("Names[0] %p,%p\n",Names[0],&Names[0][0]);
// printf("Names[1] %p,%p\n",Names[1],&Names[1][0]);
// printf("Names[2] %p,%p\n",Names[2],&Names[2][0]);
// Names[0]相当于&Names[0][0]
printf("Names[0]+1=%p,%p\n",Names[0]+1,&Names[0][1]);
printf("Names[0][0]:%c,%c\n",Names[0][0],*(Names[0]+0));
printf("Names[0][2]:%c,%c\n",Names[0][2],*(Names[0]+2));
printf("&Names[0]+1=%p,%p\n",&Names[0]+1,&Names[1]);
printf("*(&Names[0]+1)=%c,%c\n",*(&Names[0]+1)[0],*(&Names[1])[0]);
printf("*(&Names[0]+1)=%c,%c,%c,%c\n",(*(&Names[0]+1))[1],(*(&Names[1]))[1],
*(*(&Names[0]+1)+1),*(*(Names+1)+1));
printf("%c,%c,%c,%c\n",Names[2][2],*(Names[2]+2),(*(Names+2))[2],*((*(Names+2)+2)));
return 0;
}
指针
内存地址
-
字节:字节是内存的容量单位,英文称为 byte,一个字节有8位,即 1byte = 8bits
-
地址:系统为了便于区分每一个字节而对它们逐一进行的编号,称为内存地址,简称地址,系统通过对应的内存地址从而定位内存的位置
基地址
-
单字节数据:对于单字节数据而言,其地址就是其字节编号。
-
多字节数据:对于多字节数据而言,期地址是其所有字节中编号最小的那个,称为基地址。
取址符
-
每个变量都是一块内存,都可以通过取址符 & 获取其地址
-
例如:
int a = 100;
printf("整型变量 a 的地址是: %p\n", &a);
char c = 'x';
printf("字符变量 c 的地址是: %p\n", &c);
double f = 3.14;
printf("浮点变量 f 的地址是: %p\n", &f);
1,指针基础
-
指针的概念:
-
一个专门用来存放内存地址的变量,指针也就是指针变量
-
-
地址。比如 &a 是一个地址,也是一个指针,&a 指向变量 a
-
专门用于存储地址的变量,又称指针变量。
-
格式
-
类型 *指针变量名
-
解释:
-
“类型” : 指针变量指向的内存空间存放的数据类型
-
“指向” : 如果我保存了你的地址,那么就说我指向你
-
“*” :定义指针变量的固定格式
-
// 系统中给a申请了4个字节的内存空间
int a = 10;
printf("a addr:%p\n",&a);
// 定义一个指针变量用于存放a的地址
int *p = &a;
// 第一部分:*p :首先p是一个变量,占用内存8个字节,存放了a的地址
// 第二部分:int 指的是指针变量所指向的内存空间放了什么类型的数据
printf("p的值:%p addr : %p\n",p,&p);
printf("a的值:%d addr : %p\n",a,&a);
指针的定义:
// 用于存储 int 型数据的地址,p1 被称为 int 型指针,或称整型指针
int *p1;
// 用于存储 char 型数据的地址,p2 被称为 char 型指针,或称字符指针
char *p2;
// 用于存储double型数据的地址,p3 被称为 double 型指针
double *p3;
指针的赋值:赋给指针的地址,类型需跟指针的类型相匹配。
int a = 100;
p1 = &a; // 将一个整型地址,赋值给整型指针p1
char c = 'x';
p2 = &c; // 将一个字符地址,赋值给字符指针p2
double f = 3.14;
p3 = &f; // 将一个浮点地址,赋值给浮点指针p3
指针的索引:通过指针,取得其指向的目标
*p1 = 200; // 将 p1 指向的目标(即a)修改为200,等价于 a = 200;
*p2 = 'y'; // 将 p2 指向的目标(即c)修改为'y',等价于 c = 'y';
*p3 = 6.6; // 将 p3 指向的目标(即f)修改为6.6,等价于 f = 6.6;
指针的尺寸
-
指针尺寸指的是指针所占内存的字节数
-
指针所占内存,取决于地址的长度,而地址的长度则取决于系统寻址范围,即字长
-
结论:指针尺寸只跟系统的字长有关,跟具体的指针的类型无关
-
在32位系统,指针的大小占用4字节
-
在64位系统,指针的大小占用8字节
// 指针大小
int a = 10;
char b = 'c';
float c = 85.5;
char *p1 = &b;
int *p2 = &a;
float *p3 = &c;
printf("p1 size : %ld\n",sizeof(p1));
printf("p2 size : %ld\n",sizeof(p2));
printf("p3 size : %ld\n",sizeof(p3));
二,指针运算
-
指针加法意味着地址向上移动若干个目标
-
指针减法意味着地址向下移动若干个目标
-
示例:
int a = 100;
int *p = &a; // 指针 p 指向整型变量 a
char b = 100;
char *ptr = &b;
char *k3 = ptr + 2;
int *k1 = p + 2; // 向上移动 2 个目标(2个int型数据)
int *k2 = p - 3; // 向下移动 3 个目标(3个int型数据)
函数入门
1,函数的定义
-
函数头:函数对外的公共接口
-
函数名称:命名规则与变量一致,一般取与函数实际功能相符合的、顾名思义的名称。
-
参数列表:即黑箱的输入数据列表,一个函数可有一个或多个参数,也可以不需要参数。
-
返回类型:即黑箱的输入数据类型,一个函数可不返回数据,但最多只能返回一个数据
-
-
函数体:函数功能的内部实现
-
语法说明
返回类型 函数名称(参数1,参数2,.......)
{
函数体
return 返回值;
}
语法汇总:
-
当函数的参数列表为void时,表示该函数不需要任何参数。
-
当函数的返回类型为void时,表示该函数不返回任何数据。
-
关键字return表示退出函数。①若函数头中规定有返回数据类型,则 return 需携带一个类型与之匹配的数据;②若函数头中规定返回类型为 void,则 return 不需携带参数。
总结
函数名前面为返回值的类型,哪怕此函数没有返回值,也要写上void
函数的返回类型要顶格
函数返回类型与函数名空一格
函数参数要写(),哪怕里面没有任何内容,如果没有内容最好这样写void func(void)
函数调用者调用子函数的时候,可以不接收返回值
注意
注意: 大括号表示函数的工作范围,如果离开此范围,数据就不属于此函数管辖
函数不能在main(){}里面实现,虽然不会报错,如果不调用,这个子函数是不会被执行的
所有函数的实现都在主函数外面实现
2,实参与形参
-
概念:
-
函数调用中的参数,被称为实参,即arguments
-
函数定义中的参数,被称为形参,即parameters
-
-
实参与形参的关系:
-
实参与形参的类型和参数个数必须一一对应。
-
形参的值由实参初始化。
-
形参与实参位于不同的内存区域,彼此独立
-
// 函数定义中,x、y都属于形参,位于函数 max 的栈内存中
// 它们的值由实参一一对应初始化
int max(int x, int y)
{
int z;
z = x>y ? x : y;
return z;
}
int main(void)
{
int a = 1;
int b = 2;
int m;
// 函数调用中,a、b都属于实参,存储于主函数 main 的栈内存中
m = max(a, b);
}
3,函数调用的流程
函数调用时,进程的上下文回切换到被调函数,当被调函数执行完毕之后再切换回去。