发布时间:2025-12-10 19:18:40 浏览次数:7
[学习][记录] c语言:从放弃到入门9-3变量生命周期和修饰符生命周期函数的声明周期起于调用,结束于调用结束局部变量的生命周期起于调用,结束于调用结束main函数的生命周期main开始==进程的开始main函数的结束==进程的结束全局变量的生命周期 起于main调用,结束于main调用结束修饰符auto含义:只能用于修饰局部变量,表示该变量存储于栈stac…_c语言源程序的基本单位是()a
c/c++中宽窄字符串函数对应关系
起于调用,结束于调用结束希望我今天分享的这篇文章可以帮到您。
起于调用,结束于调用结束 main开始 == 进程的开始 main函数的结束 == 进程的结束起于main调用,结束于main调用结束 含义:只能用于修饰局部变量,表示该变量存储于栈stack 特点: 随用随开,用完消失 C中默认的局部变量,就是auto类型,所以通常将其省略 c++ auto自动类型 含义:只能修饰局部变量,原则上,将内存中的变量升级到CPU寄存器中存储,这样访问速度会更快,但由于CPU寄存器数量有限,通常会在程序优化阶段,被优化为普通auto类型,可以通过汇编代码来查看,优化过程(与平台和编译有关) 特点:可以避免cpu与内存的频繁交互 一般程序从内存读取数据 放到寄存器 进行运算 运算结果写入到内存关于各个存储部件中读写速度
硬盘:7200转 内存:频率1333MHZ 1600MHZ 带宽 = 频率*64/8 缓存: CPU:含义:只能用于修饰全局变量,全局变量本身是全局可用的,但是由于文件是单个完成编译,并且编译是自上而下的,所以说,对于不是在本范围内定义的全局变量,要想使用必须用extern 进行申明,如果不加上可能会造成重定义。 特点:1.可以省略,声明在前,定义在后,不论局部还是全局。 main.c : int a; other.c : int a =200;编译不会报错 2.外延性 某文件定义的全局变量 可在其他文件使用 编译过程:单文件编译 单文件每个编译成xxx.o 链接所有.o文件和lib.so文件 生成可执行文件xxx.out 特点:1.初始化值为0,且只初始化一次 2.生命周期等同于程序特点:1.全局的外延性消失,变成文件内部的全局变量 也适用于函数 2.存储在data段extern int a;
int a= 200;
是不是同一个a?
局部变量和全局变量储存的位置有什么不同?
static修饰的变量值所保存的位置在哪里 才导致是累计变化的?
保存在data段中
是双引号括起的任意字符序列看到的大小,比我们实际字面量要多一个,最后的字符’看到的大小,比我们实际字面量要多一个,最后的字符’\0’,我们称为字符串结束字符,是系统对双引号引起的字符串自动加设 的,而非手动干预。
’,我们称为字符串结束字符,是系统对双引号引起的字符串自动加设 的,而非手动干预。 字符串的存储,不像其它普通类型的常量自变量的存储一样,普通类型自变量通常存储 在代码段(text), 而字符串,则存储在数据段,且是只读数据段。 也就是通常意义上的常量区,但是常量区这种说法,是不太精确的,也不提倡。 栈区(stack):临时变量,局部变量,生命周期结束即销毁 堆区(heap):一些new出来的变量 存储的地方 特定销毁时间统一销毁,析构函数 数据段(data):用来存放程序中已初始化的全局变量的一块内存区域,属于静态内存分配。 代码段(text):用来存放程序执行代码的一块内存区域,只读,且不可修改。可能包含一些只读的常数变量,例如字符串常量等。 bbs段:bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域1.字符串数组(栈区)是将text区的常量字符串 拷贝到栈区 字符串值的改变实际上是栈的值改变 字符串指针 是直接指向 text区常量字符串地址 故不能改变text区的值2.相同点:字符数组长度大于等于字符串长度 puts函数:printf的自带换行 scanf函数:默认遇到空格结束输入,scanf("%[\n]s")遇到回车结束输入,但不检查输入长度,不安全 gets函数:空格也可以读入,但不检查输入长度,不安全。 fgets函数:参数列表(接收变量,长度,输入流) 1.printf(“”) 空串有换行功能 2.gets();不检查预留存储区是否能够容纳实际输入的数据, 换句话说,如果输入的字符数目大于数组的长度,gets 无法检测到这个问题,就会发生内存越界,所以编程时建议使用 fgets()。 char *fgets(char *s, int size, FILE *stream); fgets() 虽然比 gets() 安全,但安全是要付出代价的, 代价就是它的使用比 gets() 要麻烦一点,有三个参数。 它的功能是从 stream 流中读取 size 个字符存储到字符指针变量 s 所指向的内存空间。它的返回值是一个指针,指向字符串中第一个字符的地址。3. c中允许 c++不允许 char a[2] = "china"; printf("%s",a); 输出结果:ch 4.char * p = "china"; printf("p = %p p+1 = %p p[0]=%c 0[p] =%c\n", p, p + 1, p[0], 0[p]); printf(" = %p = %p =%c =%c\n", "china", "china"+1, "china"[0], 0["china"]); 输出结果:地址不相等 与视频不符合 1.视频说 " 字符串,则存储在数据段"而 这篇文章说了
在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等
2.字符串指针和字符数组的区别? 字符数组可修改内容,有个拷贝过程,data段常量区拷贝到栈区,字符数组实际是栈区 字符串指针如果指向data段字符串常量 是不可修改地址的,若是指向字符数组反而可以注意:被连接的串必须有足够空间
but vs 可行…
char firstName[] = "jim";char familyName[30] = "tim";strcat(firstName, familyName);printf("%s", firstName);输出:jimtim优化操作:
原本代码
while(*p) p++; char *p,*q; while(1){ p * =q*; if(p* == ' while(*p) p++;char *p,*q;while(1){ p * =q*;if(p* == '\0'){ break;}p++;q++;}优化后:while(*p) p++;while(*p++ = *q++);
'){ break; } p++;q++; } 优化后: while(*p) p++; while(*p++ = *q++); int a = 1;int* ap = &a;printf("%d \n", sizeof(ap));char * c = "123";printf("%d \n", sizeof(c));float d = 1.0f;float * dp = &d;printf("%d \n", sizeof(dp));输出:4 4 4 char *ac = “123”;
printf(“%p \n”, &ac);
printf(“%p \n”, “123”);
为什么输出地址不同?
3.字符串长度和大小是不同的
长度是指字符长度且不包含\0,大小是指字节大小包含\0
1.运行这段代码 设置字符串池优化
char * pa = "china"; char *pb = "america"; char*pc = "canada"; char*pd = "japan";char * cpArr[4] = { pa,pb,pc,pd };printf("pa = %p \n", pa);printf("pb = %p \n", pb);printf("pc = %p \n", pc);printf("pd = %p \n", pd);for (int i = 0; i < 4; i++){ printf("%p \n",cpArr[i]);}printf("----------------\n", pd);char * cpArr2[4] = { "china","america","canada", "japan" };for (int i = 0; i < 4; i++){ printf("%p \n", cpArr2[i]);}NULL ,nullptr,0区别源程序:源代码
程序:可执行文件
进程:时间概念可执行性文件被拉起,到结束的这一段过程,称为进程
进程空间:可执行文件 被拉起以后 在内存中的分布情况
栈中存放任意类型的变量,但必须是 **auto** 类型修饰的,即自动类型的局部变量,随用随开,用完即消。 内存的分配和销毁系统自动完成,不需要人工干预。 分配顺序,由高地址到低地址。 栈的大小并不大,他的意义并不在于存储大数据,而在于数据交换。局部变量申请过多,过大,char[1024*1024*10];递归层数太多 例如10000层递归堆内存可以存放任意类型的数据,但需要自己申请与释放。分配顺序,由低地址到高地址。堆大小,想像中的无穷大,对于栈来说,大空间申请,唯此,无它耳。但实际使用中,受限于实际内存的大小和内存是否连续性。(基本最大为用户空间大小)1.memset(pm,1,10*sizeof(int);
为每个字节赋值为1;一个int 实际4个字节 4个0x01 printf("%#x"); %#表示的输出提示方式,如果是8进制,在前面加0,如果是十进制,不加任何字符,如果是十六进制,会加上0x 2.calloc和malloc
calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不做初始化,分配到的空间中的数据是随机数据3.realloc
void *realloc(void *ptr, size_t size);
功能:扩容(缩小)原有内存的大小。通常用于扩容,缩小会会导致内存缩去的部分数据丢失。
参数:
void *ptr:ptr 表示待扩容(缩小)的指针, ptr 为之前用 malloc 或 者 calloc 分配的内存地址。或 ptr==NULL,则该函数等同于 malloc。
size_t:size 表示扩容(缩小)后内存的大小。
返回值:
成功返回非空指针指向申请的空间 ,失败返回 NULL。返回的指针,可能与 ptr 的值相同,也有可能不同。若相同,则说明在原空间后面申请,否则,则可能后续空间不足,重新申请的新的连续空间,原数据拷贝到新空间,原有空间自动释放。
原理:先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
问题1:指针接收 创建的内存空间(malloc),释放时怎么知道释放(free)多少内存空间的
heap的每个块儿头有保存本块的大小以及本块分配的大小。
free并不知道那个指针所指空间的大小,它要先查找当前heap的头部,然后整个块儿释放掉。
问题2:如何用malloc/new 定义一维,二维,三维数组?用free/delete销毁呢?malloc/free和new/delete区别?
问题3:malloc/calloc/realloc 区别与不同?
问题4:常见的内存泄露和检测内存泄露的常见方式?内存泄漏和野指针?
用自定义名字为已有数据类型命名。其实叫 typerename 更合适。形如:typedef 现在类型名 新类型名;typedef 是以;号结尾的 C 语言语句。而#define 则是预处理阶段的文本替换。有时
他们是可以互换的,但有时不可以。
typedef a b;#define a b1.typedef char *pChar; pChar a; a等同于?
试着预测输出并调试以下代码:
{ char *p,q; printf("sizeof(p) = %d sizeof(q) = %d \n",sizeof(p),sizeof(q));// 4,1 typedef char *pChar; pChar a,b; printf("sizeof(a) = %d sizeof(b) = %d \n",sizeof(a),sizeof(b));// 4, 4 #define DpChar char*; DpChar m,n; printf("sizeof(m) = %d sizeof(n) = %d \n",sizeof(m),sizeof(n));// 4 ,1 }2.总结
新类型名一般用大写表示,以便于区别。
用 typedef 只能声明新的类型名,不能创造新的类型,只是为已经存在的类型起
一个别名,也不能用来定义变量,即只能用其声明的类型来定义变量;
有时也可用宏定义来代替 typedef 的功能,但是宏定义是由预处理完成的,而
typedef 则是在编译时完成的,更为灵活方便。
typedef 可以让类型更见名知意,更便于移值。
问题一 初始化和赋值在c++中实际含义是?问题二 形参和实参之间传递是什么关系?问题三 typedef和#define 本质区别在哪里?问题四 结构体作形参为什么要传指针?结构体中,嵌套结构体,称为结构体嵌套。结构体中,既可以嵌套结构体类型变量,也可以嵌套结构体类型,后一种方式不推荐。首成员在低地址,尾成员在高地址。
目的是解决:一个成员变量需要多个机器周期去读的现象,称为内存不对齐。为什么要对齐
呢?本质是牺牲空间,换取时间的方法。
不同的编译器和处理器,其结构体内部的成员有不同的对齐方式
x86(linux 默认#pragma pack(4), window 默认#pragma pack(8))。linux 最大支持 4 字节对齐。
方法:
①取 pack(n)的值(n= 1 2 4 8--),取结构体中类型最大值 m。两者取小即为外对齐大 小 Y= (m<n?m:n)。 ②将每一个结构 体的成员大小与 Y 比较取小者为 X,作为内对齐大小. ③所谓按 X 对齐,即为地址(设起始地址为 0)能被 X 整除的地方开始存放数据。④外部对齐原则是依据 Y 的值(Y 的最小整数倍),进行补空操作。外对齐和内对齐:
外对齐Y:保证读取结构体的起始地址到结束地址,表示结构体之间的对齐 内对齐X:保证从结构体内变量起始的地址到结束的地址 正好是该变量的长度,结构体内成员变量之间的对齐结构体中,包含指针,注意指针的赋值,切不可向未知区域拷贝。
struct student{ char*name; int score;}stu;int main(){ strcpy(stu.name,"Jimy"); stu.score=99; return 0;}name 指针并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在
调用 strcpy 函数时,会将字符串 “Jimy” 往乱码所指的内存上拷贝,内存 name 指针根
本就无权访问,导致出错。 同样stu.name = “Jimy”;可以的,name指向常量区,但是将来name不可改
int main(){ struct student *pstu; pstu = (struct student*)malloc(sizeof(struct student)); strcpy(pstu->name,"Jimy"); pstu->score=99; free(pstu); return 0;}为指针变量 pstu 分配了内存,但是同样没有给 name 指针分配内存。错误与上面
第一种情况一样,解决的办法也一样。这里用了一个 malloc 给人一种错觉,以为也给
name 指针分配了内存。
从内向外依次释放空间。
深拷贝:拷贝内存的内容,结构体之间互不影响。浅拷贝:直接地址赋值,指针共享一片内存。一个结构体发生变化,另一个结构体也会发生变化。C 语言把文件看作是一个字符的序列,即文件是由一个一个字符组成的字符流,因 此 c 语言将文件也称之为文件流。即,当读写一个文件时,可以不必关心文件的格式或
结构。
文件,物理上是二进制,所以文本文件与二进制文件的区别并不是物理上的,而是逻辑上的。
文本文件是基于字符编码的文件,常见的编码有 ASCII 编码,二进制文件是基于值编码
的文件。
以 ASCII 码格式存放,一个字节存放一个字符。 文本文件的每一个
字节存放一个 ASCII 码,代表一个字符。这便于对字符的逐个处理,但占用存储空间
较多,而且要花费时间转换。
以值(补码)编码格式存放。二进制文件是把数据以二进制数的格
式存放在文件中的,其占用存储空间较少。数据按其内存中的存储形式原样存放。
用例:
int main() { short a = 10000;FILE * fp = fopen("ascii.txt", "w");fprintf(fp, "%d", a);//文本写fclose(fp);FILE *fp2 = fopen("bin.txt", "w");char buf[] = "abcd";fwrite(&a, 2, 1, fp2);//字节写//fwrite(buf, 4, 1, fp2);//字节写fclose(fp2);return 0;}为什么要有缓冲区(buffer) 原因为多种,有两个重点:
1 从内存中读取数据比从文件中读取数据要快得多。
2 对文件的读写需要用到 open、read、write 等系统底层函数,而用户进程每调用
一次系统函数都要从用户态切换到内核态,等执行完毕后再返回用户态,这种切
换要花费一定时间成本(对于高并发程序而言,这种状态的切换会影响到程序性
能)。
FILE 结构体是对缓冲区和文件读写状态的记录者,所有对文件的操作,都是通过FILE 结构体完成的。typedef struct { short level; /* 缓冲区满/空程度 */ unsigned flags; /* 文件状态标志 */ char fd; /* 文件描述符 */ unsigned char hold; /* 若无缓冲区不读取字符 */ short bsize; /* 缓冲区大小 */ unsigned char *buffer; /* 数据传送缓冲区位置 */ unsigned char *curp; /* 当前读写位置 */ unsigned istemp; /* 临时文件指示 */ short token; /* 用作无效检测 */} FILE ; /* 结构体类型名 FILE */在开始执行程序的时候,将自动打开 3 个文件和相关的流:标准输入流(stdin)、标
准输出流(stdout)和标准错误(stderr),它们都是 FIEL*型的指针。流提供了文件和程序的
通信通道。
如果读写的是二进制文件,则还要加 b,比如 rb, r+b 等。 unix/linux 不区分文本和
二进制文件。
作用:强制输出缓存内容 然后关闭FILE*
特点:feof 这个函数,是去读标志位判断文件是否结束的。即在读到文件结尾的时候再
去读一次,标志位才会置位,此时再来作判断文件处理结束状态,文件到结尾。如果用
于打印,则会出现多打一次的的现象
windows 换行符 ‘\n’ = 0x0d 0a;
linux 换行符 ‘\n’ = 0x 0a;
1.fprintf(fp,fmt,buff) 文本写出
fwrite(buff,size,count,fp) 字节写出
2.乱码原由
二进制文件读取由acsii码的方式读取
3.文件缓存win和linux区别
win会立即输出,linux会等待缓存满了再输出,加上\n会立刻输出缓存
4.rewind(fp) 将文件指针重置到文件头
预处理操作,不是 c 语言语句,故语句末尾没有分号,在预处理阶段完成,本质是替
换操作。
发生时段:
不带参宏:
#define 定义的宏,只能在一行内表达(换行符表示结束而非空格),如果想多行表
达,则需要加续行符。
#define PI 3.14\ 15926宏常量,常被 const/ enum 变量取代,用于定义文件路径则被常用。
#define FILEPATH "E:\\English\\listen_to_this\\listen_to_this_3"#define ERR_EXIT(m)\ do\ { \ printf("Err:%s",m);\ exit(-1);\ }while(0) //此处的分号,可有可无宏常量的缺陷 解决这一些问题,要不吝惜使用括号。
#define N 2+3 // #define N (2+3)int main(void){ int num = N*2; return 0; }宏类型:
宏可以给类型起别名,因其缺点,常被 typedef 取代
#define CHARP char *int main(void){ CHARP p,q; printf("p = %d q = %d\n",sizeof(p),sizeof(q)); return 0;}#define str(x) x
#define str(x) “aa”#x”bb” //#字符串化
#define str(x) x*2 \
x+x // ‘’ 续行符号
#undef MAX
#ifdef #ifndef
#elif
#endif
全写入,被包含的文件中。包含是支持嵌套的。
#include<stdio.h>,从系统指定路径中搜索包含头文件,linux 中的系统路径为
(/usr/include)
#include”myString.h”,从工程当前路径中搜索包含头文件,如果当前工程路径下
没有的话,则到系统路径下搜索包含。
将替换符 字符串化,解决字符串中,不可被替换的参数问题。字符串如下的书写
也是合理的。
//#define str(x) #x//#define str(x) "aaaaaaaaxaaaaaaaaa"#define str(x) "aaaaaaaa"#x"aaaaaaaaa"int main(){ printf("%s\n",str(100)); return 0;}#解决了双引号中无法替换问题,##解决了非双引号中粘连无法替换的问题。
//#define sum(a,b) (aa+bb)#define sum(a,b) (a##a+b##b)int main(){ printf("%d\n",sum(2,3)); return 0;}DATE 进行预处理的日期(“MMmm dd yyyy”形式的字符串文字)
FILE 代表当前源代码文件名的字符串文字
LINE 代表当前源代码中的行号的整数常量
TIME 源文件编译时间,格式”hh:mm:ss”
func 当前所在函数名
在打印调试信息时打印这两
个宏 FILE LINE 可以给开发者非常有用的提示
参数的个数也是可变的,也就是说,在形参表中可以不明确指定传递参数的个数和类型,一个常见的库函数 printf()就是如此。这种函数称之为变参函数。
#include <stdio.h>#include <stdlib.h>#include <stdarg.h>//num 个 int 型数相乘int mul(int num, int data1, ... ){ int total = data1; int arg, i; va_list ap; va_start(ap, data1); for(i = 1; i < num; i++) { arg = va_arg(ap, int); total *= arg; } va_end(ap); return total;}//i 个 int 型数相乘long mul2(int i, ...){ int *p, j; p = &i + 1; //p 指向参数列表下一个位置 long s = *p; for (j = 1; j < i; j++) s *= p[j]; return s;}int main(){ printf("%d\n", mul(3, 2, 3, 5)); printf("%d\n", mul2(3, 2, 3, 5)); return 0;}VA_ARGS 是一个可变参数的宏,这个可变参数的宏是新的 C99 规范中新增
的,目前似乎只有 gcc 支持(VC6.0 的编译器不支持)。宏前面加上##的作用在于,当
可变参数的个数为 0 时,这里的##起到把前面多余的”,”去掉的作用,否则会编译出错, 你
可以试试。
#define debug(...) printf(__VA_ARGS__)#define dgbmsg(fmt,...) printf(fmt,__VA_ARGS__)#define LOGSTRINGS(fm, ...) printf(fm,__VA_ARGS__) #define debug(format, ...) fprintf (stderr, format, __VA_ARGS__)#define debug(format, args...) fprintf (stderr, format, args)在标准 C 里,你不能省略可变参数,但是你却可以给它传递一个空的参数。例如,
下面的宏调用在 ISO C 里是非法的,因为字符串后面没有逗号:
debug (“A message”)
GNU CPP 在这种情况下可以让你完全的忽略可变参数。在上面的例子中,编译器仍
然会有问题(complain),因为宏展开后,里面的字符串后面会有个多余的逗号。
CPP 使用一个特殊的’##’操作。书写格式为:
#define debug(format, …) fprintf (stderr, format, ## VA_ARGS)
这里,如果可变参数被忽略或为空,’##’操作将使预处理器(preprocessor)去除掉
它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,GNU CPP 也会工作
正常,它会把这些可变参数放到逗号的后面。象其它的 pasted macro 参数一样,这些参
数不是宏的扩展。
1.补码
有符号数正负数二进制排列规律:
负数为对应的正数 取反+1
0的补码是0
2. int 和 long ,long long 区别
编译器不同 int和long的含义可能是不同的
而 long long类型是 8个字节,64位
unsigned long long:0~2^64-1
long long:-2^63~ 2^63-1
3.类型转化
显示转化
1>小数据赋给大变量 不会造成数据的丢失,系统为了保证数据的完整性,还提供了符号扩充行为。
2>大数据赋给小变量 会发生截断行为,有可能会造成数据丢失
隐式转化
1>整体提升 32位机中 所有低于32位的整型数据 在运算过程中先要转化为 32 位的
整型数据,然后才参与运算
2>混合提升
(1)有long double 类型参与运算,先全部转化为long double
没有(1)则(2)有double 类型参与运算,先全部转化为double类型
没有(2)则(3)有unsign int 类型参与运算,先全部转化为unsign int类型
没有(3)则(4)有 int 类型参与运算,先全部转化为int类型
3>强制转化
每次压栈前会保存当前函数环境包括变量值,然后新创建一块栈区来进行操作。
每次出栈会销毁当前栈顶,最后return到上一个函数所在地址。
数组是用于存储相同数据类型数据,且在内存空间连续的一种数据结构类型。数组 三要素。
类型:type [N]
定义:type name[N]
大小:sizeof(type [N]) 或 sizeof(name)
int array[10] = {1,2,3}; //部分初始化
int array2[10] = {[3] = 10};
int array1[10] = {0}; //清零
int array3[10] = {1,2,3,4,5,6,7,8,9,0,1,2};//越界不检
数组名是数组的唯一标识符,数组的每一个元素都是没有名字的。数据名有两重含 义:
(1)数组名作整体访问
int main(void) { int arr[10] = { 1,2,3,4,5,6,7,8,9,0}; //int[10] arr; printf("sizeof(arr) = %d\n",sizeof(arr)); printf("&arr = %p\n",&arr); printf("&arr+1 = %p\n",&arr+1); int(*pa)[10] = &arr; printf("pa = %p\n",pa); printf("pa+1 = %p\n",pa+1); return 0; }输出结果:
sizeof(arr) = 40&arr = 007AF9DC&arr+1 = 007AFA04pa = 007AF9DCpa+1 = 007AFA04pa +1 为数组最后一个元素的后的第一个地址
(2)数组名作起始地址访问成员
name[i] == *(name+i) == i[name]int main(void) { int arr[10] = { 1,2,3,4,5,6,7,8,9,0}; printf("arr[0] = %d\n",arr[0]); printf("*(arr+0) = %d\n",*(arr+0)); printf("arr = %p\n",arr); printf("arr +1 = %p\n",arr+1); printf("&arr[0] = %p\n",&arr[0]); printf("&arr[0] +1 = %p\n",&arr[0]+1); return 0;}输出结果:
arr[0] = 1*(arr+0) = 1arr = 010FF7E4arr +1 = 010FF7E8&arr[0] = 010FF7E4&arr[0] +1 = 010FF7E8参数传递,旨在传递三要素(起始地址,步长, 范围)
void selectSort(int *p,int n)// int 表示步长,p表示起始地址,n表示范围char * allocMem(int n) { char *p = (char*)malloc(n); return p;}int allocMem(char **p,int n){ *p = (char*)malloc(n); return *p== NULL?-1:1;}二维数组的本质是一维数组,只不过,一维数组的成员又是一个一维数组而己。三 要素:
type name[M][N] == type[N] name[M] int arr[3][4] == int[4] arr[3] // 步长为int[4] 起始地址为arr 范围3行可以省,列不可以省,部分初始化和清零依然适用
int array[2][3] = {[1][2]=3}; //c99
int array[][3];=> int[3] arry[];//行可以省 列不可以省 行 初始化 自动赋值
数组名,是数组的唯一标识符。
(1)数组名作为整体访问
int main(void) { int arr[3]; //int[3] arr; printf("sizeof(arr) = %d sizeof(int[3]) = %d\n",sizeof(arr),sizeof(int[3])); printf("&arr = %p\n",&arr); printf("&arr+1 = %p\n",&arr+1); int array[3][4]; //int[4] array[3] type array[3]; type[3] array; printf("sizeof(array) = %d sizeof(int[3][4]) = %d\n",sizeof(array),sizeof(int[3][4])); printf("&array = %p\n",&array); printf("&array +1 = %p\n",&array+1); return 0;}输出结果:
sizeof(arr) = 12 sizeof(int[3]) = 12&arr = 005CFDF4&arr+1 = 005CFE00sizeof(array) = 48 sizeof(int[3][4]) = 48&array = 005CFDC0&array +1 = 005CFDF0&array+1 同一维数组 数组结尾地址
(2)数组名作起始地址访问成员
(3)结论
a 表示第 0 行首地址,a+i 表示第 i 行首地址
*(a+i), a[i], &a[i][0]表示第 i 行第 0 个元素地址 *(a+i)+j, a[i]+j, &a[i][j]表示第 i 行第 j 个元素地址 *(*(a+i)+j) *(a[i]+j), a[i][j]表示第 i 行第 j 个元素二维数组在逻辑上是二维的,但是在存储上却是一维的。正是这个特点,也可以用 一维数组的方式去访问二维数组的。
int main() { int array[2][3] = { 9,8,7,6,5,4}; for(int i=0;i<2; i++) { for(int j=0;j<3;j++) { printf("%d ",array[i][j]); } putchar(10); } int *p = (int *)array; for(int i=0; i<6;i++) { printf("%d ",p[i]); } return 0; }二维数组本质是常量数组指针,所以跟其对应的形参也应该是数组指针类型的变 量
void displayArray(int(*p)[4],int n) //步长int[4] 首地址p,范围n一次可以移动一个字节指针,称为 char 类型的指针,
一次可以移动二个字节的指 针,称为 short类型的指针。
一次可以移动一个数组大小的指针,是什么类型的指针的,又该如何称谓呢?
比如 二维数组名: arr[3][4]; arr+1 一次加 1 的大小,就是 int[4]类型的大小,即一个数组 的大小。
int [N] *pName; =>int (*pName)[N];
语法解析:“()”的优先级比“[]”高, “*”号和 pName 构成一个指针的定义, 指针的类型为 int [N]。
typedef int (*TYPE)[N];TYPE a;//等价于 int (*a)[N];| 解析 | 一维数组名 | 二维数组名 |
|---|---|---|
| 示例 | int arr[4] | int arr[3][4] |
| 本质 | 一级指针 | 数组指针 |
| 引用 | &arr 数组指针 | &arr 数组指针 |
(1)二维数组传参
void displayArray(int(*p)[4],int n)(2)一维空间的二维访问
int main() { int arr[12] = { 1,2,3,4,5,6,7,8,9,10,11,12}; int (*p)[2] = (int(*)[2])arr; for(int i=0; i< sizeof(arr)/sizeof(int[2]);i++) { for(int j=0; j<2; j++) { printf("%d\t",p[i][j]); } putchar(10); }}输出结果:
1 2
3 4
5 6
7 8
9 10
11 12
type name[x][y][z] == type [y][z] name[x]; //步长为 type[][],首地址name,范围 x对变量取地址,取得是最低位字节的地址。32 位机下,大小均为 4。
int main(void) { char a; short b; int c; printf("&a = %p\n",&a); printf("&b = %p\n",&b); printf("&c = %p\n",&c); printf("sizeof(&a) = %d\n",sizeof(&a)); printf("sizeof(&b) = %d\n",sizeof(&b)); printf("sizeof(&c) = %d\n",sizeof(&c)); return 0;}输出结果:
&a = 00CFF897&b = 00CFF890&c = 00CFF88Csizeof(&a) = 4sizeof(&b) = 4sizeof(&c) = 4指针的本质,就一个有类型的地址。
int main(void) { int a = 0x12345678; printf("%p\n",&a); printf("%d\n",*(&a)); //printf("%d\n",a); //printf("%x\n",*((int*)0x0060FEAC)); return 0;}内存的地址,即指针(常量),存放该地址的变量就是指针变量。此变量,必须满足 3 个条件,大小为 4,有类型,区别于其它变量。
type *var;type 决定了类型(步长),* 表示该变量是指针,var 用于存储地址。
指针变量 大小始终为4字节
数组变量 大小为 类型(即步长)* 范围 字节
int main(void) { char *pa; int *pi; printf("sizeof(pa) = %d sizeof(pb) = %d\n", sizeof(pa),sizeof(pi)); printf("sizeof(char*) = %d,sizeof(int*) = %d\n",sizeof(char*),sizeof(int*)); char *pm = (char*)malloc(100); printf("sizeof(pm) = %d\n",sizeof(pm)); int (*parr)[10]; printf("sizeof(parr) = %d\n",sizeof(parr)); int arr[100]; printf("sizeof(arr) = %d\n",sizeof(arr)); printf("sizeof(&arr) = %d\n",sizeof(&arr)); return 0;}输出结果:
sizeof(pa) = 4 sizeof(pb) = 4sizeof(char*) = 4,sizeof(int*) = 4sizeof(pm) = 4sizeof(parr) = 4sizeof(arr) = 400sizeof(&arr) = 4类型,决定了,从 var 存放的地址开始的寻址能力。
int main(void) { int a = 0x12345678; char *pa = &a; printf("%x\n",*pa); short *ps = &a; printf("%x\n",*ps); int *pi = &a; printf("%x\n",*pi); return 0;}输出结果:
78567812345678注意:值的存储 由低到高
int main(void) { int a = 0x12345678; printf("%x\n",*&a); int arr[3]; printf("arr = %p\n",arr); printf("&arr = %p\n",&arr); printf("arr+1 = %p\n",arr+1); printf("&arr+1 = %p\n",&arr+1); printf("*&arr = %p\n",*&arr); printf("*&arr + 1 = %p\n",*&arr+1); return 0;}输出结果:
12345678arr = 006FFAC4&arr = 006FFAC4arr+1 = 006FFAC8&arr+1 = 006FFAD0*&arr = 006FFAC4*&arr + 1 = 006FFAC8在地址 方面,
数组arr => &arr ,也有arr => *&arr
但arr+1 !=> &arr+1 (步长不一样)
指针的运算是,地址值+类型的运算。
int main(void) { int a[10]; printf("a = %p\n",a); printf("a+1 = %p\n",a+1); printf("&a[9] - &a[4] = %d\n",&a[9] - &a[4]); printf("(int)&a[9]-(int)&a[4] = %d\n",(int)&a[9] -(int)&a[4]); return 0;}输出结果:
a = 0116FAE8a+1 = 0116FAEC&a[9] - &a[4] = 5(int)&a[9]-(int)&a[4] = 20解释几点:
a和a+1是 运算是以步长(此时为int)为单位 +1即移动一个Int长度
&a[9],&a[5]都是转为指针int* 也是以int为步长单位的,所以9-5+1=5步
(int)&a[9],(int)&a[4] 被强转为字节为单位的了 所以结果为 5*4 =20字节
补充几个例子:
例1
int main() { int a[5] = { 1,2,3,4,5}; int *ptr1 = (int *)(&a + 1);//此时ptr1指向a末尾后面一个int int *ptr2 = (int *)((int)a + 1);//此时ptr2指向a起始地址后面一个字节 printf("%x, %x",ptr1[-1], *ptr2);//ptr前移一个int 即a[5],ptr2取一个int长度包含了a[0]后三个字节和a[1]第一个字节 return 0;}输出结果:
5, 2000000
解释
int *ptr1 = (int *)(&a + 1);//此时ptr1指向a末尾后面一个int
int *ptr2 = (int *)((int)a + 1);//此时ptr2指向a起始地址后面一个字节
printf(“%x, %x”,ptr1[-1], *ptr2);//ptr前移一个int 即a[5],ptr2取一个int长度包含了a[0]后三个字节和a[1]第一个字节
例2
int main(int argc, char **argv) { int a; int *p = &a; printf("%x, %x\n",p,p+1); printf("%x, %x",(int)p,(int)p+1); //printf("%x, %x",(void*)p,(void*)p+1); return 0;}输出结果:
84fd44, 84fd48
84fd44, 84fd45
同理,(int *)和(int)步长区别
二级指针,是一种指向指针的指针。我们可以通过它实现间接访问数据,和改变一级
指针的指向问题。
二级指针 实际上存储着一级指针的地址
(1)改变一级指针指向的内容
(2)改变一级指针指向
同理 N级指针 是为了间接访问N-1级指针数据
FILE与 sqlite3 函数设计的比较:
FILE* file = fopen(filePathStr,modeStr); // 返回句柄 不利于错误判断
if()file == NULL) return -1;
sqlite3 *db = NULL;
int rc = sqlite_open(&db,sql,NULL,NULL); //错误码 和句柄分开返回
if(rc == SUCCESS)
FILE 和 sqlite3 均是描述资源的句柄(即一个结构体)。获取文件句柄的方式是通过
返回值,而获取数据库句柄的方式是参数。
FILE *的设计方式,并不是很好。原因是,返回值中要容错出错码。而最合理的
方式,是将出错码和返回值分开。
步长为一级指针长度 即4 bytes
作用增强程序的键壮性。但凡被修饰过的变量,其值不可修改
const 修饰变量,此时称为常变量。常变量,有常量的属性,比用宏定义的常量有 了类型的属性
特点:声明时就必须定义,且定义之后不可修改。且发生在编译时期。
可以通过指针修改const修饰的局部变量,但不可以修改const全局变量
const int b = 100;int main(void){ const int a = 100;printf("temp a = %d\n", a);int *pa = &a;*pa = 300;printf("after temp a = %d\n",a);int *pb = &b;printf("global b = %d\n", b);pb = 250;printf("after global b = %d\n", b);system("pause");return 0;}输出结果:temp a = 100after temp a = 300global b = 100after global b = 100请按任意键继续. . .(1) const 修饰指针,表示指针的指向是恒定的,不可更改。
int * const p = &a;const修饰p,指针p是不可变的,也就是p指向的内存单元不可变。即p的指向不可变,p指向的内存单元的内容可以变。
(2) const 修饰指针指向的值 不能改变
const int *p = a;
a的值不能改变
(3) const int* const p = &a;
*p和p都被const修饰了,所以p指向的内存单元,和p指向内存单元中存放的内容都是不可变的。
函数的本质是一段可执行性代码段。函数名,则是指向这段代码段的首地址。
回调(函数作参数)
回调函数,本质也是一种函数调用,先将函数以指针的方式传入,然后,调用。这种写法的好处是,对外提供函数类型,而不是函数定义。这样我们只需要依据函数类型 和函数功能提供函数就可以了。给程序的书写带来了很大的自由。
练习
解释((void() ()) 0)();
地址为0的函数强转为(void(*)())函数的调用