C学习笔记

C里面的注释 是/**/
// 是C++的注释

数据类型:简单类型,结构类型,指针类型

简单类型:基本类型(int,float,double,char,void(无类型)),用户定义类型(enum)
结构类型:数组,结构struct,联合union,类class
指针类型:*

数据类型的本质:创建变量的模子,固定内存大小的别名,编译器可以根据不同类型的数据分配不同大小的内存空间
typedef 给数据类型取别名


变量:不赋值,它的值是随机的,本质是:连续一段内存空间的别名(不占内存),内存空间的标号
给变量赋值就是往那个空间写数据,不是给标号写数据,变量保存在代码区(入栈、出栈是指那块空间,不是指标号)


常量:一个数字、字符串等等就是常量,不能被修改的,程序一开始就会出现在内存中(地址不会变),直到程序结束


字面量:
int a = 0;
int w[100];
0,100,是字面量,不保存在内存里面,不能对其取地址,但字符串在内存


没有用到任何库函数,可以不用#include 导包

宏代码块(带参数的宏)由预处理器处理,只是文本替换,没有任何编译过程
#define  :宏定义,只是简单的文本替换,预编译后就替换为 代表的内容了
#define xzc xiechanc
#define xzc(a)  a+a*2        // 带参数的宏

int n =xzc(5); // 使用宏,n==15


#define printf(M) printf("%s\n",#M);
printf(xzc);    // #号就是自动加上“”  -->"M"

#define Y(x,y) x##y
int mydata = 10;
int a = Y(my,data);    // a就是10,##就是把两段连接起来(##就是把##引用的宏参数拼接在后面),就是mydata,就是10


#define Run(M) system(M):printf("%s",M);
Run("calc");    // 冒号,连接两个语句,宏后面带一个分号

#define Run(M) system(M): \        // 一行写不到,用\表示下面的内容也是宏
        printf("%s",M);

#define LOG(format, ...) fprintf(stdout, format, __VA_ARGS__)
其中,...表示参数可变,__VA_ARGS__在预处理中为实际的参数集所替换(固定写法)


宏 与 函数调用的区别
#define MYFUNC(a, b) ((a) < (b) ? (a) : (b))
int myfunc(int a, int b)
{
    return a < b ? a : b;
}

int main()
{
    int a = 1;
    int b = 3;
    //int c = myfunc(++a, b);
    int c = MYFUNC(++a, b);  //((++a) < (b) ? (++a) : (b))    // 只是简单的文本替换,不会运算++a

    printf("a = %d\n", a); //2  //3
    printf("b = %d\n", b); //3  //3
    printf("c = %d\n", c); //2  //3

    return 0;
}

void fun1()
{
    #define c 20
    #define a 10    // 从这行开始,之后所有行的代码都可以用该宏(不用管作用域)
    const int b = 20;
    //#undef        // 取消前面 #define 定义的所有宏,即该行之后 a,c 这个宏不再有效
    //#undef a       // 取消指定的某一个宏
}

void fun2()
{
    printf("a = %d\n", a);
    //printf("b = %d\n", b);    // b常量使用作用域的,在这里用引用不到了
}
提示:遇到不会用的关键字,可以去查看系统库的代码、系统头文件里面怎么用
多去看看那些代码


不存在void类型的变量,它的类型大小也不确定,看具体强转为什么类型

进制转换:
    1.任意进制 与 十进制互转都可
        短除法->任意进制
        幂次法->十进制
    2.二进制 到 其他进制,向到十进制,再由十进制转
        如进制 是2的幂次,则有简单方法,从右往左,每几位(幂次数)一小段,表示一位
            1110010 -> 八进制:001 110 010   152
            1110010 ->十六进制:0111 0010    72

b代表bit(位),B代表BYTE(字节),字(word),双字(DWORD)
网速 单位一般是bit,b/s
磁盘大小 单位一般是byte,一般是大写B,GB、MB
1 Byte = 8 bits

1 Kb = 1024 bits
1 KB = 1024 bytes
1 Mb = 1024 Kb

1 MB = 1024 KB 

1KBps=8Kbps

8b=1B,2B=字,2字=双字

sizeof(int),是关键字,运算符,返回数据类型占多少字节,可以sizeof(int),也可以sizeof(a) (int 型的变量)
    它是操作符,表示函数,它返回的值在编译期间就确定了
    总之就是返回所给地址那一片空间的大小,代表的那片空间的大小
size_t:数据类型,sizeof永远返回的是一个大于等于0的整数,用int来表示sizeof的返回值不合适
    size_t一般就是一个无符号的整数(有的系统可能是无符长整数,占的字节与系统有关)
    size_t n=10;

char w[]="xxx";
sizeof(w)= 4

char w[10]="xx"
sizeof(w)= 10

int w[10];
sizeof(w)= 40
sizeof("12345")    == 6,// \0  占了1字节
测量指定的东西占了多少字节,(变量/类型申请的空间)

char w[10]="xxx";
sizeof(w)= 10

unsized int  n;
unsized long  n;
...
...

int a=10;
a=!a;    // a为0了,非0为真,取反为0(位数全为0了)
a = 10>9    //a 为 1

赋值兼容:就是类型的sizeof值Y一样,占内存大小一样,占字节多的类型兼容占字节少的(占字节少的可以赋给占字节多的)
隐式类型转换:
    double <- float
    ^|
    long
    ^|
    unsigned
    ^|
    int <- char short
    

循环
C没有bool类型变量,bool是C++的
0为假,非0为真(包括负数)


int a=50; 在内存中开辟一个4字节的空间
    50->0x32,十六进制
    内存中的地址用十六进制表示,一个字节该数加一
       一个数,在内存中用16进制表示,一个字节表示十六进制的两位(高位补0,因为四个位可以用一个16进制数表示)
    所以在内存中 0032 表示如下:
    00110010 32    地址:0x000100
    00000000 00         0x000101
    00000000 00         0x000102
    00000000 00         0x000103

    0x12345678 存放如下:
    01111000 78    地址:0x000100
    01010110 56         0x000101
    00110100 34         0x000102
    00010010 12         0x000103

    
符号位也是计算到一个16进制数里面
    

一个int型数据占据4个字节的内存大小,在16位操作系统下,int是2个字节,在32和64位操作系统下,int是4个字节。

小端对齐:内存中,高地址存放数的高位,底地址放数的低位
    (先是转化成16进制数,从右到左取数(两个),每一个地址放两个16进制数,升序(地址)依次放过去)
    注意:不是位倒过来,单个字节里面,位还是正常表示数的
大端对齐:
对于大型unix CPU都是按照大端对齐方式处理int,
但对于x86构架CPU,还有ARM(手机CPU),是小端对齐的


32位系统
short 2字节
long 4字节
int  4字节
long long 8字节

64位系统
int 4
long 大部分机器8字节,与机器相关

在VS里面可以配置项目的系统配置:
    Debug-配置管理器-平台:可以可以选择CPU类型(ARM:手机CPU,x64:64位系统),系统位数

整数溢出:
    将大字节类型的数 赋值给 小字节类型的数时,装不下,只取低位
    (二进制位来赋值)


char 1字节,代表一个字符,本质还是一个数(ASCLL码,总共127个ASCLL字符,最高位为0)
unsigned char
可表示最大的无符号数 ff
可表示最大的有符号数 0x7f(127),最小是-128

\a,警报
\b退格:光标先前一格,后面打印的字符会把 最后那个字符 替换掉
\n换行
\r回车:光标回到最前面,后面打印的字符会依次替换字符
\t制表符
\\斜杠
\’单引号
\”双引号
\?问号


printf格式打印:
/*
printf(),默认打印字符串
不会进行类型转换
%d,%i -  int
%5d -  数占的最少字符空间数
%05d -  数占的字符空间数,不足的补0,右对齐
%-5d -  数占的最少字符空间数,左对齐
%ld – long int
%lld    long long型
%int64d    int64型
%hd – 短整型
%c  - char
%f -  float
%6.5f - 保留5位小数,占6字节,不足6位补空白
%06.5f - 保留5位小数,占6字节,,不足6位补0
%lf – long double
%llf – long long double
%u – unsigned int    无符号10进制整数,unsigned char 可以用该格式数据,长度大的格式可以输出长度小的变量
%x – 十六进制输出 int 或者long int 或者short int,%#x:显示0x
%X - 十六进制字母为大写
%o -  八进制输出,%#0,显示0
%s – 字符串
%ms   输出字符串占m列,右对齐。
%p &n -    输出内存地址
%-ms  输出字符串占m列,左对齐。
%hu -无符号短整数
%m.ns  输出字符串前n个字符,占m列,右对齐。
%.*s
printf("%.*s\n", 5, "123456");    // 用一个参数指定输出字符个数
%%  输出一个百分号
%e / E - double    科学计数法表示的数,此处"e"的大小写代表在输出时用的“e”的大小写
%g / G  根据数值的大小,自动选择用f格式或e格式输出 实数。输出时选择占宽度较小的一种,且不输出 无意义的零
“\””   输出一个双引号

Posix的%zu(小写的z)    size_t 类型
Gnu的%Zu(大写的Z)
 32位环境下size_t被定义为unsigned int, 64位环境下size_t被定义为unsigned long

char buf[10]="我";
printf("%c%c",buf[0],buf[1]);    // 两者输出是一样的,"我"占2字节,会合并显示出来
printf("%s",buf);

数字后缀:
10  int
10u 无符号int
10l long int


double float 相当于 double,
*/

char w[]="中国a日本"
中文用两个字节表示,为了与一个字节的ASCLL字符区分
表示中文的两个字节 分成一个单独的字节 其字节首位都是1(ASCLL是0),即是一个负数

char *p="";
char a=*p;    // 空字符串其实就是一个 '\0',a就是一个'\0'(整数0)

toupper(),将字符串转换为大写


弹对话框
win32 控制台 空项目
.c文件
<Windows.h>
MessageBoxA(0,"内容","标题",MB_OK);// 参数2显示的内容,参数3标题,参数4显示的按钮
MessageBoxW(0,L"内容",L"标题",MB_OK);// 加 L 表示是宽字符(多字节字符集,有中文了,多个字节表示一个字符)
MessageBox(0,"内容","标题",MB_OK),与代码的编码有关,或者也加L
MessageBox(0,TEXT("内容"),TEXT("标题"),MB_OK),也可以用一个TEXT宏,任意类型都不会乱码了,兼容
直接这样执行,是在一个单独的进程里面,可注入到其他进程里面显示对话框
生成dll(不需要main函数) 动态连接库,被注入exe程序中执行
右击项目-配置属性-常规-配置类型-dll(由于在建立项目时,建立的不是dll项目,可改属性)
用注入工具注入
选择dll、填写要执行的函数
程序转到进程(在任务管理器里面,查看程序所在的进程)


宽字符(占多字节)、窄字符(占一个字节)
#include<locale.h>//本地化
void  main13()
{

    setlocale(LC_ALL, "zh-CN");//设定中文,不可少
    wchar_t ch = L'我';//汉字当作字符,加 L 表示是宽字符
    printf("%d\n", sizeof(ch));    // 2字节
    wchar_t *pch = L"锄禾日当午";
    
    putwchar(ch);
    _putws(pch);
    wprintf(L"%lc", ch);//lc打印宽字符
    wprintf(L"\n%ls", pch);//ls打印宽字符串
    //封装一个宽字符串,增删查改

    system("pause");
}

大战僵尸外挂,进程之间不能互相修改内存中存的,只能修改自己内部的值,所以用dll注入
用内存查找工具,查找阳光的内存地址,写一个程序修改阳光值

代码操作电脑,触发键盘、鼠标数据,指定鼠标位置
<Windows.h>
WinExec("natepad",SW_MAXIMIZE);    // 打开记事本,并最大化
keybd_event('A', 0, 0, 0);//第一个参数是表示哪个键,第三个参数0代表按键
keybd_event('A', 0, 2, 0);//第一个参数是表示哪个键,第三个参数2代表松开键
keybd_event(VK_CONTROL, 0, 0, 0);//按了ctrl键
keybd_event(VK_SPACE, 0, 0, 0);//按了空格键
keybd_event(VK_SPACE, 0, 2, 0);//放开空格键    ,切换输入法
keybd_event(VK_CONTROL, 0, 2, 0);//按了空格键
keybd_event(VK_RETURN, 0, 0, 0);// 按下回车键
keybd_event(VK_RETURN, 0, 2, 0);
    
SetCursorPos(10, 20); // 设置鼠标的位置
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);// 鼠标左键按下
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);    // 鼠标左键抬起


float、double、long double
运算效率不高,占几个字节与操作系统有关
long long 占8字节,在32为系统中运算效率也不高,int 4字节是最高的(系统支持范围内,效率最好)
超过寄存器 位数范围了,long long 在运算时会开启两个寄存器来运算,效率就低了

字符串是内存中一段连续的char空间,以’\0’(一个字节)结尾,'\0'ASCLL码就是一个0,可以将一个整数0赋值给数组结尾,表示字符串结束

volatile:告诉编译器不要自作聪明的给我优化代码,把我的变量优化的寄存器里面计算,
    只要是volatile类型变量,每一步都需要从内存当中读取/放回内存中。(线程同步、防止别其他程序修改值)
register:register告诉编译器,这个变量只是用寄存器就好,提高效率,所以说register只是一个建议,
    而不是必须的结果(寄存器空间不足的时候不会那么做)。
    (与编译器有关,VC只是建议,GCC就是强制)
    寄存器变量没地址,不能获取它的地址
printf,字符串
putchar,字符、数字都行(不换行)
scanf("%s",buf),不安全的函数,-定义一个宏跳过警告,输入只是输入到缓存区,回车才写到buf内,空格分隔
scanf("num=%d", &num);//必须精确对应

getchar,任意字符(包括转义字符)
输入输出类型对应(%d,%......)

++i,i++
使用变量 前/后 该变量值马上加1
++a表示取a的地址,增加它的内容,然后把值放在寄存器中;
a++表示取a的地址,把它的值装入寄存器,然后增加内存中的a的值;

int a = 0;
int c = 0;
c=a++;            // 赋值运算,后 加1
c = a++ + a++ + a++;    // c为3,表达式结束后a为3,三个0运行相加(已经运算了,执行后加操作),后各自加1,再赋值给c
c = a++ + a++ + a++ + ++a; // 运算中加了个 ++a前置(加法运算前,四个1相加),相加后执行后加操作 各自再加1,再赋值给c,a为4 c为7,不知道为什么,看具体编译器

逗号运算(C语言只是一个规范,它最后的值与编译器有关(等于左边,或者右边),不同厂商的编译器可导致不同结果,也与系统有关)

运算优先级,没必要全部搞清楚,对于模糊的地方加括号即可,不影响效率

-------------------25
C、C++本来就是两种语言,只是存在兼容性,但不完全兼容,编译器是通过后缀区分的(.c、.cpp),用不同的规范去编译代码

只要不是0,就是真(包括负数),C没有布尔类型


goto 语句/标识 ,跳到指定代码块执行,可跳过代码,少用(不好阅读,程序乱),
标识块之下的代码最好不要出现变量定义(避免重名,执行一次定义一个变量,名字相同了)
goto语句可以出现在 同一个函数中的 任何位置
goto、和要goto的标签 必须在同一函数,两者可以在同一函数的任意位置

int main()
{
    int i=0;
    if(i == 0)
    {
        goto my;
// 只是加个标签,方便goto调用,并不影响:括号的之下的代码执行(即没有goto语句跳到这里,之下的代码也会一行一行执行)
funTest:
    printf("倒回来了\n");
    }
    printf("好啊好啊\n");
    
test:
    i++;
    printf("%d\n",i);
    printf("哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈!\n");
    
    for(i=0;i<10;i++)
    {
my:
    printf("自增 %d\n",i);
    }
    printf("退出循环了!\n");
    if(i<12)
    {
        goto my;    
    }
    
    return 0;
}
自增 0
自增 1
自增 2
自增 3
自增 4
自增 5
自增 6
自增 7
自增 8
自增 9
退出循环了!
自增 10
退出循环了!
自增 11
退出循环了!


for(int i;i<10;i++),老版本的C编译器出错,不支持在里面定义变量
gcc a.c -std=c99,指定在c99模式下编译代码,就支持那么写了(1999年之后的标准)

数超出类型范围了,注意达不到判断条件


整数在计算机内部的存储方式:都是补码表示的,运算都是补码运算 输出的二进制是补码 还是补码转成原码输出?
右移<<操作,负数为什么不会变符号,因为操作的是补码,移除1还有1,如
-1:补码:1111111110,原码:1000000000001


1.原码:
+7的原码是00000111
-7的原码是10000111

2.反码:
一个数如果值为正,那么反码和原码相同
一个数如果为负,那么符号位为1,其他各位与原码相反
+7的反码是00000111
-7的反码是11111000

3.补码:
原码和反码都不利于计算机的运算,如:原码表示的7和-7相加,还需要判断符号位,位操作等等
计算机内部都是存补码的,调试、看内存中变量的值时,都是补码
正数:原码,反码补码都相同
负数:最高位为1,其余各位原码取反,最后对整个数 + 1,
    (  >整个数<,具体数 自己去人工算出来(不管越位,把数算出来,加个-号)),一个字节最小的负数 -128 就是这么来的
    (数值超过、覆盖符号位了,还是这个数,记得加-号即可,不管越位)
    (数值没超过、覆盖符号位的,不用管符号,符号位不记在数值内,算出值加个-号即可)

    最多表示255个数    
    有符号位、补码形式保存数,则如下:
    00000000    0
    00000001    1
    00000010    2
    ........
    01111111    127
    10000000    -128    (计算机就是用补码的,能表示-128这个数,具体要人工自己算)
    10000001    -127
    ........
    11111111    -1

补码符号位不动,其他位求反,最后整个数 + 1,得到原码

用补码进行运算,减法可以通过加法实现
7-6=1
7的补码和-6的补码相加:00000111 + 11111010 = 100000001
进位舍弃后,剩下的00000001就是1的补码

-7+6 = -1
-7的补码和6的补码相加:11111001 + 00000110 = 11111111
11111111是-1的补码


printf("%d",10.3);    
printf("%f",10); // printf输出只是在内存中拿取一定字节的数据按浮点型解析,输出不一定是10了
        // 不同的数据类型,会有不同结果
        // 浮点型表示比较特别
        // 最好强转后输出

5.位运算
注意:所有操作都是对补码操作的,输出时再转成原码
所有位,包括符号位的二进制都参与运算

反(~),与(&),或(|),异或(^)

    int len =4;
    (len&1);所有位,包括符号位的二进制都参与运算

    ((byte)~127)): 01111111->10000000,运算的是补码,符合位也参与运算,10000000 转为源码就是-128

>>、<<、>>>(无符号右移)

<<:移动超出左边边界的位则直接抛弃,在后面添0
>>:左边的空白位,全部填充原数的最高位,可以保证符合不变
   如果左操作数是无符号类型,或者左操作数是带符号类型但为非负值,则左边多出来的位用 0 来填充
   如果左操作数是负值,那么由编译器决定用于填充至左边多出来的位的内容,可能是 0,也可能是符号位
>>>:左边的空白位,全部填充0,不能保证符合不变,Java中特有的

劫持:一个函数调用另一个函数(从一个地址的指令 跳到 另一地址的指令)
    jmp指令跳到指定地点,劫持就是改变jmp跳转的目的地址,跳到自己的函数地址,在自己函数内控制程序运作
    改变函数指针的值
例子:不缴费不可用(就是跳转到指定函数判断的)
已经有人实现劫持的代码了,Detours

1.得到库

2.劫持自己,避免死循环,不能调用要劫持的函数了,通过函数指针调用

3.劫持别人,生成dll,注入到其他程序,修改别人jmp的函数地址

4.劫持系统,劫持系统函数CreateProcessA();生成dll,随便找一个正在执行的程序注入

5.劫持网络,socket可被劫持了,腾讯都自己参照TCP协议自己实现了代码

Detours 的express(体验)版本,不能全局劫持,也不能原码级别劫持,只能劫持库函数等等
                (即只能劫持库(dll,lib)里面的函数,没有封装的,原代码函数劫持不了)
专业版功能强大些,都可劫持
都要release模式,debug模式本身就是去劫持别人了,

MFC->多个文档,选项卡式文档,视觉样式和颜色 黑色主题->复合文档支持 无->文件拓展名 cpp(自己随便取),预览、缩略图搜索处理支持
->数据库支持 无 -> 高级功能 全部勾上 -> 基类 CRichEditView

乱码解决:源文件-xxxDoc.cpp-添加CRichEditDoc::m_bRTF = FALSE;(void CCppIDEDoc::Serialize(CArchive& ar)函数内)
资源视图-Menu-编辑菜单,右击-添加事件程序(添加点击事件)

建立MFC项目,禁用按钮(不能点击,就是该按钮收不到消息而已),再写一个控制台程序,给那个按钮发消息即可破解
(window 是基于消息的)
工具查找句柄 / 代码获取句柄,句柄跟内存地址一样 每次启动都不一样(类名,标题,返回句柄),(句柄,是否显示)
发消息
<Windows.h>
SendMessage(0x000444557,WM_LBUTTONDOWN);    // 参数1句柄,参数2消息(按钮按下)
SendMessage(0x000444557,WM_LBUTTONUP);        // 按钮抬起,(一次点击事件)
// 用VS-工具-Spy-查找窗口,工具去获取句柄
改标题(按钮、窗口....,都有句柄)
SetWindowTExtA(0x1245252,"哈哈");// 修改指定句柄的内容(任何控件,获取句柄就可改变了)

显示、隐藏窗口
改变QQ窗口大小、位置(句柄,0,x,y,width,height,0),转圈
禁用窗口
获取鼠标位置
<Windows.h>
HWND win = FindWindowA("TXGuiFoundation","QQ");// 获取一个窗口的句柄
// 参数1该窗口的类名,参数2标题(这两个,在程序写好之后都不会变了)
if(win == NULL)
return;
ShowWindow(win,SW_HIDE);// 隐藏窗口,参数1窗口的句柄
ShowWindow(win,SW_NORMAL);// 显示窗口

SetWindowPos(win,0,0,0,20,30,0);// 改变窗口大小
// 参数1句柄,参数3 4窗口X、Y坐标,参数5 6窗口宽高

EnableWindow(win,FALSE);// 设置窗口不可用
EnableWindow(win,TRUE);// 设置窗口可用

struct tagPOINT pt;
GetCursorPos(&pt);    // 得到鼠标当前位置
SetWindowPos(win,0,pt.x,pt.y,500,500,0);
-----
HWND win = FindWindowA("TXGuiFoundation", "QQ");
    if (win != NULL)
    {
        RECT rectwind;//区域,lefr ,right ,top,bottom
        GetWindowRect(win, &rectwind);//获取窗口区域
        SetWindowPos(win, NULL, rectwind.left, rectwind.top-100, 300, 300, 1);
    }

-----------------27
数组:内存连续,存同一类型
int w[5];
int w[]={0,1,2,3,4},定义一个5元素的数组,并初始话了 
int w[5]={5};将数组所有元素初始化为5,(字节分配空间的时候就初始化了)
    // memset(w,5,5); 这是调用函数初始化了,比在定义是初始化效率低
int w[5]={0,1,2,3,4},分别初始化每一个元素
int w[5]={0,1},初始化前部分元素,后面没初始化的补0了
w 代表数组第一个元素的地址 w == &w[0],*w就是取这个地址的值(本质是 *地址 -> *0x87455854)

定点赋值,只给2,3角标的赋值
int w[5]={[2]=5,[3]=10}

数组首元素的地址和数组地址是两个不同的概念
数组名代表数组首元素的地址,它是个常量。
解释如下:变量本质是内存空间的别名,一定义数组,就分配内存,内存就固定了。所以数组名起名以后就不能被修改了。
数组首元素的地址和数组的地址值相等

数组名是数组首元素的起始地址,但并不是数组的起始地址
通过将取地址符&作用于数组名可以得到整个数组的起始地址
w(一级指针),&w(二级指针)


int n;
int *p=&n;
p+1;    // n(变量名)就代表那一块4字节的空间(整片空间),&n是取那一块空间的地址(首地址),p+1的步长就是n所代表的空间(移动4字节)

int a[10];// a(变量名)代表整片空间,&n是取这片空间的地址(返回一个地址,是指针,占4字节),同样&a也是一样的
&a+1,a+1,结果不同,前者前进了40字节,后者只前进了4字节
(a代表那一块40字节的空间,&a取空间(整个数组)的地址,&a+1步长40字节,a+1首元素地址,int类型,步长4字节)

数据类型分为基础、非基础,思考角度应该发生变化
C语言中的数组有自己特定的类型
数组的类型由元素类型和数组大小共同决定
例:int array[5]的类型为int[5]
数组类型:

    typedef int(MYINT5)[5];   //int
    typedef float(MYFLOAT10)[10];
数组定义:
    MYINT5 array;// 就相当于   int array[5];
    array[0]=100;


数组指针:用于指向一个数组
1)通过数组类型定义数组指针: 
typedef int(ArrayType)[5];
ArrayType* pointer;
2) 声明一个数组指针类型 typedef int (*MyPointer)[5];    // 用()括起来,改变优先级,表示*先修饰MyPointer,即MyPointer是一个指针类型
MyPointer myPoint;
3)直接定义:int (*pointer)[n];  
    pointer    为数组指针变量名

int a[5];
pointer = &a;    // 数组取地址是一个数组指针,int (*p)[5],不是int **p这样的
for (i=0; i<5; i++)
{
    (*pointer)[i] = i;    // 获取指针变量的值,是一个数组
}

int (*)[5],是一个数据类型,(去掉变量名)


// 开辟一个一维空间,可以用二维、三维等等的形式访问,
// 只是指针步长(地址运算是移动步长)不同而已,空间还是一样的
void main()
{
    int *p =(int*) malloc(sizeof(int)* 40);//一维数组
    for (int *px = p,i=0; px < p + 40; px++,i++)//
    {
        *px = i;//赋值
        printf("%d,%p\n", *px, px);//指针循环
    }
    //int b[5][8];
    printf("\n\n\n");
    int(*pp)[8] = (int(*)[8]) p;//指针类型决定了访问的方式
    for (int i = 0; i < 5; i++)
    {
        for (int j = 0; j < 8; j++)
        {
            //printf("%5d", pp[i][j]);//打印数据
            printf("%5d", *(*(pp + i) + j));// pp[i][j]
        }
        printf("\n");
    }
    //a [4][2][5]
    printf("\n\n\n");
    int(*ppp)[2][5] = (int(*)[2][5])p;
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            for (int k = 0; k < 5; k++)
            {
                // printf("%5d", ppp[i][j][k]);//打印元素
                printf("%5d", *(*(*(ppp + i) + j) + k));
            }
            printf("\n");
        }
        printf("\n\n\n");
    }

    system("pause");
}


改变桌面背景(20(表示换背景),0,图片地址,3(马上生效))
SystemParametersInfoA(20,0,"F:\\a.jpg",3);

rand(),产生随机整数数,(每次执行程序都是那一排随机数)
srand(),指定种子,种子相同,产生的一排随机数是一样的(算法就是根据种子来产生的),
time(NULL),返回当前系统时间
difftime(t1,t2);    //返回小数, t1-t2
int t = GetTickCount(),获取当前时间

播放声音(0,具体操作,操作对象,0,0,显示还是隐藏程序界面),system("");函数会出现命令行
ShellExecuteA(0,"open","F:\\b.mp4",0,0,SW_NORMAL);//SW_HIDE


二维数组
int a[3][5]={{},{},{}},定义并初始化
int a[3][5]={,,,},也可以线性初始化
a的本质是一个数组指针,指向数组低维,指向int[5]这种类型,是这整块空间的地址,是二级指针,*(a+1)首元素地址
a同样也是首元素的地址    
a+1,步长4*5个字节


n维数组
n维数组的数组名,本质就是一个指向数组的指针(指向n-1维(低n-1维,后面的那n-1维)数组的数组指针)
如:
char arr[2][3][4];
arr:就是一个char[3][4]的指针,arr+1 -> 步长3*4=12
arr[0]:相当于arr数组削减了一维(三维数组中 2 中的一个元素(二维数组)),就是一个char[4]的指针 arr[0]+1 步长为4
    即 是一个二维数组char[3][4],的首地址(最大那一维,取一个元素,是一个二维数组)
arr[0][0]:类似的,类似的削减,是一个一维数组char[4],的首地址,步长 为 1
 


字符串就是一个char的数组(记得有'\0',遇到\0后面的字符被忽略,C语言没有字符串)
char w[5]={'','',''.....}初始化
char w[]={'','',''.....}初始化
char w[]="xxx",初始化,自动加 \0,"xxx"在静态区,并把字符数据复制给在栈区的w数组去
char w[10]="xxxx",之后的字符都是数字0
char *arr[]={"dada","faf44"}    // 指针数组,每一个指针就是一个字符串,数组长度为2
printf(w);

char c = "1234"[0];    // 获取字符串的某一个字符,在C++中就不能这样了

GBK,两字节表示一个中文字(两个char来表示),都大于128(这是无符号,有符号的就是负数,都是负数)
UTF-8,占3字节

scanf(),消除警告! ->在文件开头 加上#pragma warning(disable:4700),或者用宏 #define XXXX(代码首行,#include前)
#include <>
#pragma warning(disable:4700)    // 数字表示要压制 哪种警告
int main(){

}


不检查溢出,用scanf_s(),可以检查溢出,溢出了就不会
scanf_s("%d", &n);  // 输入一个整数,与scanf一样的用法
scanf_s("%s", buf, 5);    // 只是输入字符串时,指定可输入的最大长度,超过了,只截取指定的字符数
scanf_s("%s %d", s,5,&n); // 字符串溢出了,后面的整数也不会读取了

溢出:输入超出数组长度了,把地址后面的地址给填充了,导致内存中的数据变化(影响其他变量的值,或者其他程序的数据)
不能输入空格(认为字符串结束了,回车也是结束)

gets(),可输入空格(回车认为结束,回车不读入),但没有检查溢出

fgets(a,3,stdin),(数组,最多输入长度-包含了\0 具体输入会少一个字符 sizeof(a),输入设备)
检查溢出,可输入空格,回车(表示结束,但回车也会输入)

puts(),打印字符串会自动加一个回车
fputs(s,stdout);不自动加回车输出

字符串函数
strlen(),返回字符串长度(中文占2字节),不包含结尾的\0
strlen(NULL),会导致程序异常,因为没有字符,也没有\0,NULL与""不是一回事,NULL表示空指针,""表示还有一个字节保存着"\0"

strcat(a,b),把b追加在a后面,a必须有足够空间,有溢出(读不到b的 \0 就会一直读,一直追加,导致a越界)
strncat(a,b,sizeof(a)-strlen(a)-1),指定还可以放多少(减1,减去b中\0占的位置)

strcmp(a,b);= 0,> ,有溢出
strncmp(a,b,4),只比较前4个字符

strcpy(a,b);b 复制到 a去,\0也会复制进去,有溢出(读不到b的 \0 就会一直读,一直复制进去,导致a越界)
strncpy(a,b,2);复制n个字符复制进a,记得减1,\0占的空间,都是从数组开头开始复制

memset(a,0,sizeof(a)),将a的0 - sizeof(a) 的元素只为0
memcpy(arr1,arr2,100),将arr2的数据复制到arr1,复制100字节

int memcmp(const void *buf1, const void *buf2, unsigned int count);
memcmp();比较内存区域buf1和buf2的前count个字节。
将buf1 第一个字符值减去buf2,ASCLL码相减
当buf1<buf2时,返回值<0
当buf1=buf2时,返回值=0
当buf1>buf2时,返回值>0


sprintf(s,"aa7%d",i),格式化字符串,并放入数组(只是输出位置不同,其他的与printf一样)
sprintf(s,"aa7%d%c",i,c),几个%格式,后面就几个参数
都有溢出,不检查数组长度,只管处理
最后会加一个 \0

strchr(a,'w'),查找指定字符,返回指针 输出会从那个位置不停输出,直到遇到\0,没有就返回空NULL 就是一个0

strstr(a,"xx"),查找子字符串,返回找到的指针位置  输出会从那个位置不停输出,直到遇到\0,没有就返回空NULL 就是一个0

strtok(a,'@');分隔字符,返回@前的字符串(就是首字符的指针)
strtok(NULL,'@');继续找,返回分割的下一串,没有了就返回NULL(就是0)

atoi("123"),将字符串转化为int整数
atof(), ... 小数
atol(), ... long类型
--------------------------28
system("pause");暂停

scanf("jiafh%d",&a);输入时要输全 jiafh454

函数
申明:void fun(int,double);,或者void fun(int a,double f);
申明可任意多次,定义只能一次
形参在函数调用前并不占内存
在调用函数时,才分配形参地址,首先返回地址入栈,形参入栈,函数运行体....
GCC编译器对没有定义的函数 就调用,默认函数返回值是int类型

int fun(int );如果传字符串进去,c语言会把字符串的地址传进去

传值(函数里面改变不了 传入变量的值,其实是函数调用时开启了另外的内存空间,定义了另为变量,该变量值就等于传入的值)
传址(可改变 传入的变量值,数组算传址,数组名就代表首元素的地址,是一个地址可*arr获取该地址的值)


有返回值,不写return语句,返回的是一个随机数
exit(),退出程序,参数相当于main函数的返回值(程序的退出码)
main函数的return 代表程序结束

递归缺点:耗资源,难理解,(Linux系统在资源耗尽时,会自动重启,Android重启可以这么搞)


可变参数:
C语言头文件:<stdarg.h>
C++头文件:<cstdarg>
a_40


多源文件 编译
win32 控制台程序 -空项目    / 或者 就是空项目 

如果在系统目录下查找文件,用include <>
如果在用户当前目录下查找文件,用include "",兼容<>
在另一个.c文件里面只定义一个方法,在当前.c文件中调用,只要申明方法即可调用
但是一般是将函数申明放在.h的文件里面,再在当前.c文件中include(只是把文件复制进来而已)

条件编译:
避免重复被include(避免重复申明),可以在.h文件中 定义宏
在引入头文件、定义头文件时可以这样写
#ifndef _AH    // 如果没有定义_AH 这个宏
#ifdef _BH     // 如果定义了_BH
#define _AH
#else        // 或者
void fun();
int add(int,int);

#endif    // 结束if

#define A 10
int main()
{
   #if (A==5)    或者 #if A==5
     {  }
   #elif(A==9)
     {  }
   #else
     {  }
   #endif
}
都要加#,A用到的值也是宏的,编译时不是宏的访问不到
#endif,一定要有,不然之后的代码也会算成条件的一块
也可以不用大括号,多条语句只要是在条件之间的都会编译


#ifdef A             或 #if defined(A)
{ }              {  }
#endif             #endif
如果定义了A的宏定义就编译

#ifndef A             或 #if !defined(A)
{ }              {  }
#endif             #endif
如果没有定义了A的宏定义就编译

--------------

dll:不能直接运行
把自己写函数给别人用,可以给源文件(包含.h头文件),也可以将代码变成好,搞成二进制文件(dll动态连接库)给别人调用
c++不能在c中用,c的函数在c++中可以,需要时加载,不需要时释放(节约资源),多个程序加载同一个库,内存中只有一份

查看dll里面的函数,输入命令:dumobin /exports xx.dll

.lib:静态库,调用时可不包含头文件(可不用申明库内函数)
多个程序加载同一个静态库,内存中有多份
可私有,不让别人用


生成dll:
方式1:
win32 控制台程序
建立一个c的dll空项目,建立.c文件,在里面定义函数
(右击项目-配置属性-常规-配置类型-dll(由于在建立项目时,建立的不是dll项目,可改属性))
如果建立的不是空项目,加#include "stdafx.h",(非空项目,会有该头文件生成)

    __declspec(dllexport)    // 表示该函数要生成dll,被别人调用
    int mymax(int a, int b)
    {
        if (a > b)
            return a;
        else
            return b;
    }

    __declspec(dllexport)
    int myadd(int a, int b)
    {
        return a + b;
    }
方式2:
建立一个c++的dll空项目,建立.cpp文件,在里面定义函数
如果建立的不是空项目,加#include "stdafx.h"
#include "stdafx.h"
extern "C" //在C++语言当中,extern "C"告诉编译器(c++的关键字),用C语言的方式编译这个函数
{       //因为c++的函数不能在C中调用,加上这个编译出来的代码,才能被c调用(但是加了这个,C++代码就不能调用该库了)
       // 也可以不用{}括起来,每个函数前加,extern "C"  int mymax(int a, int b){}
    __declspec(dllexport)    // 没有该行,不会将函数导出dll,dumobin命令自然就看不到该函数了
        int mymax(int a, int b)    // 但是 导出的函数可调用、返回 未导出的函数,于是外界可以通过已导出的函数,调用未导出的函数
    {
            if (a > b)
                return a;
            else
                return b;
        }

    __declspec(dllexport)
        int myadd(int a, int b)
    {
            return a + b;
        }
}


使用dll:
1.将dll放在自己项目的debug目录里面(项目根目录的那个debug),dll里面函数入口地址、函数的汇编代码
2.将.lib文件放在.c文件的同级目录(lib文件 是资源描述文件,是对dll文件的描述,dll有哪些函数,函数入口地址等等)
    (函数调用时,编译器会通过lib的描述,找到dll里面的函数,再调用)
3.在代码中#pragma comment(lib,"xxx.lib")    // 这时Windows的微软的代码,不是c本身的
  int add(int,int);    // 申明函数,没有上一句是不能被调用的,这只是申明,没定义
  (3这个步骤一般都写在头文件中去,库的头文件也放在 自己项目.c文件的同级目录,加载到VS放在"头文件"里面里)
.....

或者自己加装库
// 指定路径加载动态库
HMODULE mydll = LoadLibraryA("动态库.dll");
typedef void  (*pmsg)();//简化函数指针
pmsg1 = (pmsg)GetProcAddress(mydll, "msg");//获取函数地址
pmsg1();//执行
// 释放库
FreeLibrary(mydll);

dll与so封装
执行.exe文件:就是exe到内存中执行,用到dll的函数时,就会加载对于dll到内存(动态库就是用到时,才加载)
编译的时候指定动态库,用到的函数记录下来
升级方便

静态库:在编译生成exe的时候就将lib(静态库)代码编译进去,编译后 执行不再需要静态库
运行时,静态库一起加载到内存(没用到也加载到内存),运行时速度快

Linux下的动态库不叫dll,叫.so,或者.sa
静态库后缀.a

静态库生成:
gcc -Wall myadd.c -o myadd.o    // 生成目标文件
ar rcs libmyadd.a myadd.o     // ar是gnu归档工具,rcs是参数,r表示替换,c表示创建,s表示生成
// 静态库只是将目标文件压缩成一个.a文件

// 使用静态库
gcc -Wall -L. mymain.c -o main –lmyadd

// -static,表示连接时,如果同时有静态库、动态库,强制链接时链接静态库
// 默认是连接动态库
gcc -static -o a.out a.o –lmyadd


// 连接多个库
gcc -Wall -L. mymain.c -o main –lmyadd  –lmyadd2 –lmyadd2 –lmyadd3
nm xxx.exe 查看可执行程序里面的函数,标号
nm liba.a  查看里面的函数,标号
-A:每个符号前显示文件名;
-D:显示动态符号;
-g:仅显示外部符号;
-r:反序显示符号表。

(C语言编译不会改变函数名,C++会,加入了函数参数信息(方便函数重载),add(int ,int)-->addii)
所以, C 与 C++库不一样,所以写库最好用C写

然后直接执行程序即可

一个程序在执行时,系统会根据内存情况给程序分配一个 程序入口地址(内存执行是随机的)
程序里面的函数,也有一个函数地址(它的位置是,基于程序入口地址,偏移一定量 得到,这个偏移量在编译时就确定了)
但动态库dll、so 里面的函数没有确定的偏移量

Linux下生成动态库.so
1.一样的在.c里面定义函数(不用加 __declspec(dllexport))
2.编译生成 与位置无关的代码 (即动态库里面没有函数地址的偏移量) -fPIC:生成位置无关的代码 
    -fPIC :同时表示生成的动态库可以放在任意目录里面使用,不加该参数,库只能放在/home/用户/lib里面
    gcc -c a.c -fPIC -o a.o
3.链接生成动态库 -shared:表示生成动态库(库名必须是libxxx.so)
    gcc -shared -o libxxx.so a.o
或者
gcc -shared -fPIC a.c -o libxxx.so
// 2、3步合成一块

4.生成.h头文件,不用#pragma comment(lib,"xxx.lib") 这样引入函数定义
    #ifndef _AH    
    #define _AH

    void fun();
    int add(int,int);

    #endif
nm xxx:查看动态库里面的函数
file xxx.so:可以看到一下信息
libgtkspell.so(库名): ELF 32-bit LSB shared object(文件类型), Intel 80386(指令集),
version 1 (SYSV), dynamically linked, stripped

5.使用库,编译(指定动态库,不然找不到函数定义,找不到库),执行main.out即可
  编译一次,把可执行程序、库一起带走 在任意哪个用户、目录下都可执行,不用再编译
gcc –o main.out -L. -la main.o

-L.    表示在当前目录下查找,可以指定其他目录
-la    表示查找a这个库(不用写liba.so)(其实是查找liba.so,liba.a文件,.so存在时,优先用.so文件)
gcc –o -Ixxx main.out -L. -la main.o
// 在工程编译时,-I,指定在哪个目录查找头文件

// 编译的时候直接连接系统库,不用指定目录,不去连接库,系统也会自动去连接
gcc –o main.out  -la main.c
ldd main.out    // 查看可执行程序连接了哪些库(动态库、静态库),以及库内依赖的其他库,库的位置
file main.out    // 查看文件属性,可在多少位系统、什么CPU架构下执行


动态库的搜索路径:Linux在执行程序过程中,会去查找所需要的动态库,
方式1:在系统的/usr/lib,/lib目录去查找(/lib64 64位系统的),
      那两个lib路径可在/etc/ld.so.conf.d,/etc/ld.so.conf 中配置
      (用到哪个模块的库,在里面配置路径即可,
      安装软件就会在/etc/ld.so.conf.d或/etc/ld.so.conf配置这个软件的库路径)
      相当于Windows的system32目录,放在里面的库所有程序都可用,都可去查找(不建议把动态库放进去)
方式2:配置.bash_profile文件的LD_LIBRARY_PATH属性(系统就是去它指定的路径查找库的),将自己保存库的目录添加进去
    LD_LIBRARY_PATH=$HOME/lib:/usr/lib,
    或者->只添加 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.(效果一样,都是将路径加入环境变量)
    $HOME:用户名下的lib目录,自己建的目录
    (“$PATH”引用变量,表示原先设定的路径仍然有效)
    :冒号表示分割
    
    如果不行再添加以下变量:
    LIBRARY_PATH=$LIBRARY_PATH:/usr/local/mysql/lib
    export LIBRARY_PATH


Linux下(Windows也行),C语言封装的库,C++不能直接用,在头文件中做如下处理:
    加export(c++的关键字),告诉g++编译器这是C实现的库,编译器自动做一些处理,C++就可调用了
    #ifndef _AH    
    #define _AH
    export "C" void fun();
    export "C" int add(int,int);
    #endif
    或者,包在一起
    #ifndef _AH    
    #define _AH
    export "C"
    {
        void fun();
        int add(int,int);
    }
    #endif
但是加了export,在C中又不能调用该库了,为了在c、c++中都能调用
#ifdef __cplusplus  // __cplusplus是c++编译器预定义的一个宏,c编译器没有,这样就可以区分了
extern "C"
{
#endif

int max(int a, int b);
int add(int a, int b);

#ifdef __cplusplus
}

#endif    


--------------------
C、C++混合编程(同一工程C、CPP文件都有),一般都将C搞成库,很少这样混合编程
a.c:
void fun()
{

}
a.h:
#ifndef _AH    
#define _AH
export "C"    // 因为C的函数编译后函数名不变
{
    void fun();
}
#endif

b.cpp:
#include "a.h"    // CPP的函数编译后函数名变了,而在a.c中编译出的函数名没变,导致在a.c中找不到函数了(所有加export )
void main()
{
    fun();
}
--------------------

Linux默认不在当前目录下查找文件(运行main.out时会找不到库文件),要配置(只在当前用户下有效)
配置环境变量   :冒号表示分割

1
cd
2
vi .bash_profile
3 添加以下配置,=号前后不要加空格,或者在path变量后面加个 . ,path=:xx:xxx:.
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
4
保存退出
5
. .bash_profile        使配置生效(两点之间有个空格),第一个点代表执行的意思
或者
source .bash_profile
或者
用户终端退出,重新登录一下
或者
关机重启系统

------------------PATH和LD_LIBRARY_PATH
PATH:  可执行程序的查找路径
修改:用户名/.bashrc 或 用户名/.bash_profile或系统级别的/etc/profile
1. 在其中添加例如export PATH=/opt/ActivePython-2.7/bin:$PATH
2.生效
    source /etc/profile 或者 .  /etc/profile,( . 和 /etc/profile 有空格)


LD_LIBRARY_PATH: 动态库的查找路径
修改:用户名/.bashrc 或 用户名/.bash_profile或系统级别的/etc/profile
1. 在其中添加例如export LD_LIBRARY_PATH=/opt/ActiveP/lib:$LD_LIBRARY_PATH
2.一样的生效

#在PATH中找到可执行文件程序的路径。
export PATH =$PATH:$HOME/bin
#gcc找到头文件的路径
C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/local/mysql/include
export C_INCLUDE_PATH
#g++找到头文件的路径
CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/mysql/include
export CPLUS_INCLUDE_PATH
#找到动态链接库的路径
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/mysql/lib
export LD_LIBRARY_PATH
#找到静态库的路径
LIBRARY_PATH=$LIBRARY_PATH:/usr/local/mysql/lib
export LIBRARY_PATH


echo $PATH,在命令行直接输入,查看变量的值,是否正确

关闭终端,再打开,以便更新设置
-------------------------30

c语言直接与硬件打交道

指针:占4字节,64位系统是8字节
变量在内存中有一个内存地址,变量的值就是保存在这个地址处的(该地址保存的值就是该变量的值)
有个地址、值 的区分

指针:本身也是一个变量,有内存地址、有它的值,只不过它的值是一个内存地址(该地址就是一个32位整数)
    指针是数据类型,这个数据类型是 它所指向的内存空间
    (该空间就是一个数据类型,指针虽然只保存首地址,但这个数据类型就代表多大的空间)
    (所以 指针 p+1 的步长就不同了)

    在C++编译器的角度,指针只是一个变量,不管几级指针,都是4字节,除此之外啥也不是
    编译器不关心指针所指向的空间是什么样子(不在乎一维,还是二维,几维),程序员要使用那片空间时,自己关心
    指针值本身就是一个32位数,本质还是要看 *那片内存块*
    指针变量 与 指针变量指向的内存块 是两个不同的概念


    在汇编级别,有远指针、近指针区分,C语言级别没有
    内存有 段 的概念,每一个段有它的基地址,指针所在的地方 的 段 也有基地址,
    指针的段基地址 与 它指向的地址的段基地址 相同 就叫近指针,不同就叫远指针
    近指针:指针 保存的就是一个偏移量,它指向的地址的段基地址 加上偏移量,就是指向空间的地址了
    远指针:CPU中有一个 段地址寄存器 ,段地址寄存器 的值 加上指针的值 就是远指针 指向空间的值

&a,    //取a变量的地址
int *p=&a;
*p,   // *地址(如本质是 *0x87455854),    读取p那个地址的值,具体值看指针类型,然后把所占的字节 里面的数取出来
a=*p; //把地址的值 赋值给变量
*p=a,// 把地址的值 赋值为a

int a=NULL;
int *p=NULL;空指针(不指向任何变量),空指针释放任意次都没关系,但是指向存在的内存地址不能被释放两次
其实NULL就是0,0处的地址不可操作(没有该地址,或者系统保护地址),
不初始化指针,指针的值是随机的,叫 野指针,非常危险,不能不初始化 就去赋值

操作系统发现进程 修改 其他进程的地址的值(该进程之外的地址),操作系统就会 强制终止该进程

内存分:内核内存区域、用户内存区域
用户内存区域的 进程地址是独立的,用户进程只能访问自己进程的内存地址,不能访问其他进程、内核区域的地址
内核内存区域中 内核进程之间的内存 可以自由访问
内核内存区域的进程 不能访问 用户内存区域的内存,用户内存区域的进程 不能访问 内核内存区域的内存,

指针也有类型,赋值时最好类型对应,至少类型兼容(int 与 unsigned int 类型的指针可互相赋值)
void *p;  // 任何类型的指针可以赋值给void类型的指针 (void:无类型,该类型的指针只是代表一个指针类型,指向什么有程序员具体决定)
    // void 类型的指针 也可以 赋值给任何类型,赋值个什么类型的指针就相当于转换为那个类型了,可直接用了
    // (在C语言中可以,在C++中不可以,要强转)
    // p++,是不行的,步长不明确,不知道移动多少,p+5 是可以的指明了移动量
虽然赋值给 void类型的指针,但C语言并不知道它的类型,不能使用该地址的值(因为不知道类型,不知道该地址具体占多少字节),用的时候必须将类型强转
int a;
void *p=&a;
int b = *(int *)p    // 强转之后才能去取值
int *p=(int *)10,    // 将一个数字强转为一个地址 之后才可赋值给指针(不然是一个int数,不是一个地址)


int w[10];
int *p=w;
sizeof(w)=40,sizeof(p)=4,    // p是指针类型(不论什么类型的指针都占4字节),占4字节
w就是值第一个元素的地址,&w C语言是取数组第一个元素的地址(&w与w的值是一样的,但含义不一样),*w 与 w[0]的值是一样的


int *p,*p1,n;    //p p1都是指针,n是整数
int* p2,p3;    // p2,p3都是指针

int array[10];
int *p=array;
p++;    //指针运算不是指针的地址值运算,而是移动到下一个元素(这时*p==array[1])
    // 移动到下一个元素,其实移动了4字节
char str[10];
char *p=str;
p++;    // 移动到下一个元素,其实移动了1字节
    // 所以具体移动多少字节,与类型占的字节数 有关
    // void 类型的指针,不能移,因为不知道类型,不知道移动多少

array++; //是不行的,数组名是一个常量,你不能像指针一样移位
p=array+1;// 是可以的,移到下一个元素

char w[]="hfaofhklaf";
char w1[10];
w1=w;    // 是不行的,数组名是一个常量,不能赋值
w = "xzc";// 是不行的,数组名是一个常量,不能赋值,只能是在定义事赋值


*p++    //相当于*(p++),取值 位置再+1   ,不是取值 值再+1 (*的优先级大于++)
p[i]    // 可以这样用指针取数组的值,C语言内部 还是按 *(p+i) 处理

但是
char *p="hello";//将指针指向一个常量(常量也是保存在内存中的(常量池),有其地址,p就指向了该地址,*p='A',不行,静态区只可读不可写)
char a=p[1];    // 可访问
p[1]='A';// 不行,不能改常量的值,指向一个 char数组则可以这样改值

int a=1;
int b=33;
int *pp=&a;
pp[0]; // 可以这样取指针的值,pp[1]移一元素,可取到相邻地址的值


int w[]={1,2,3,4};
char *p=(char *)w;
p=p+3;
*p;    // 其实指针就是移动了3字节,取出这时的值 就是数字元素w[0]的第4个字节的值(因为这是char类型,只读取1个字节)
    // 0x01  0x00  0x00  0x00 ,*p值就是0
    //读取p那个地址的值,具体值看指针类型,然后把所占的字节 里面的数取出来


指针数组
char *p[10];    // 定义一个指针数组,每个元素都是指针变量,可用来存一个变量的地址
        //(每一个元素可以存一个变量/数组的地址,可像上面操作数组一样,去操作)

p+1    // 代表数组第二个元素,不是 指向字符串的下一个字符,
    // 不能p++(p是数组名,是常量 不能运算,要是被改变了,那一块内存不能回收了,不知道从哪里开始回收),移动了4字节


char *p[10];
p[0] = "第0个元素";
p[1] = "a第1个元素";

char a = *(p + 1)[0];    // 获取的字符是'a'


char a='A';
char str[20];
p[0]=&a;
p[1]=str;
p[2]="21ddajk554";

sizeof(char *)==4;
sizeof(p)==40    // 一个指针类型占4字节,10个指针元素的数组就40了


数组指针


数组作为形参,C语言就是把数组当做指针
int fun(int arr[])    // 就是fun(int *arr)
{
sizeof(arr) == 4;    // 当做指针了,可*(arr+i),与指针一样的用法
}


int w[] = {1,2,3,4,5,6,7,8,9,10};
printf("%d",*(w+5));    // 其实指针 数组一样的用,只是数组名运行(不能 w++)
            // 数组名就相当于一个指针变量,保存的是一个地址

指针的指针(二级指针)
指针本身也是一个变量,也有地址,可以定义指向它的指针
int a;
int *p=&a;
int **p1=&p;
int ***p1=&p1;    // 多级指针,以此类推可无数级

**p1=100;    // 就是给a赋值,
***p2=100;


int *p0;
int **p1=&p0;
int ***p2=&p1;
p0=(int *)&p2;    // 指针本身是一个变量,保存的是一个地址,(这个地址其实就是一个数,可强转赋值)
***p2==p2的地址;    //
****p2 == p1的地址;    // 指向 已经形成一个环了,依次取值(只取地址的值,不管是不是强转的),会循环

交换两变量的值,不用第三个变量,会溢出
a=a+b;    // 两个值搞在一起
b=a-b;    // 这时a-b,就是a的值
a=a-b;

不越界的方法;异或(异或加密法)
a = 9;
b = 3;

a 0000 1001    9
b 0000 0011    3

a=a^b    // a = 0000 1010     10,中和两个数
b=a^b    // b = 0000 1001    9,从中和里面去掉一个数,剩下另一个
a=a^b   // a = 0000 0011

形参是数组,可不用写长度
int fun(int arr[]){}


二级指针内存模型
1.
char *arr[10];        // 二级指针的一种形式
arr[0]="dafagadg";
void pout(char **p,int len)    // 这种形式的二级指针,形参可这样写
{
    int i=0;
    for(i=0;i<len;i++)
    {
        printf("%s\n",p[i]);    // 也可这样使用
    }
}

2.
char arr[2][3]={"ccccc", "aaaa", "bbbb","11111"};// 二级指针的另一种形式
char arr[][3]={"ccccc", "aaaa", "bbbb","11111"};    // 第二维的长度不能省略(数组指针的步长就不确定了),第一维可以省略(几个字符串可动态决定)
低维是3,高维是2,内存结果如下:内存其实还是线性连续的,3字节为一个单元
内存永远是线性的,几维几维,只是人为抽象的
______^______
| |3| | |3| |
-------------
int printAarray02(char pArray[10][30], int num)        // 做形参时,...
{
    int i=0;
    for(i=0;i<len;i++)
    {
        printf("%s\n",p[i]);
    // 这种形式的二级指针内存模型,形参写成arr[][],才可这样用,不能写成**p,写成char *(p)[30]
    // (因为一个数组元素只占一个字节,不能用来保存地址),不能这样用了
    }
}

3.
char **p = (char **)malloc(sizeof(char *) * 100); 
// 对于编译器来说,只是开辟了400字节空间,里面是什么数据,几级指针不在乎,返回这一片空间的首地址
p + 1,移动4字节,指向这100单元空间的第二单元空间,这些空间里面存什么数据,程序员自己怎么认为都行
p[2] = (char *)malloc(sizeof(char) * 20); // 第三单元的空间存的是一个地址,相当于 *(p+2)=(char *)malloc(sizeof(char) * 20);
p[2][5],取 第三单元的空间存的地址 的第6个字符,相当于*(*(p+2)+5)

注意:
int arr[10];
void fun(int **p){}    //不能fun(&arr),因为&arr 是一个数组指针 int (*)[10],不是二级指针


#include <stdio.h>
#include <string.h>
#include <stdlib.h>


// 分割字符串
int str_spit(const char *str,char **p,int *num)
{
    int res = 0;
    

    if (str == NULL)
    {
        res = -1;
        printf("输入的字符串str = NULL,error_code=%d\n",res);
        return res;
    }
    if (p == NULL)
    {
        res = -2;
        printf("没有分配空间存放结果p = NULL,error_code=%d\n", res);
        return res;
    }
    if (num == NULL)
    {
        res = -3;
        printf("没有分配空间存放字符串条数 num = NULL,error_code=%d\n", res);
        return res;
    }
    int n = 0;
    int len = 0;
    while (p[n][len] = *str)
    {
        if (p[n][len] == ',')
        {
            if (len > 0)
            {
                p[n][len] = 0;
                len = 0;
                n++;
                str++;
            }
            else
            {
                str++;
            }
            continue;
        }

        len++;
        str++;
    }
    if (len > 0)
    {
        n++;
    }

    *num = n;
    return res;
}
// 输出
int my_print(const char **p, int num)
{
    int res = 0;
    if (p == NULL)
    {
        res = -1;
        printf("要打印的内容p = NULL,error_code=%d\n", res);
        return res;
    }

    int i;
    for (i = 0; i < num; i++)
    {
        printf("%s\n",p[i]);
    }

    return res;
}

int str_sort(char **p,int num)
{
    int res = 0;
    if (p == NULL)
    {
        res = -1;
        printf("要排序的内容p = NULL,error_code=%d\n", res);
        return res;
    }

    char *tmp;
    int i,j;
    for (i = 0; i < num-1; i++)
    {
        for (j = i + 1; j < num;j++)
        {
            if (strcmp(p[i], p[j]) > 0)
            {
                tmp = p[i];
                p[i] = p[j];
                p[j] = tmp;
            }
        }
    }
    return res;
}

void main()
{
    char *str = ",,bbbbb,,,,cccccc,aaaa,,,,,aaa,,,,ddddddd,,,,,eeeeee,,,,,";

    int num;
    char **p = (char **)malloc(sizeof(char *) * 100);

    p[0] = (char *)malloc(sizeof(char) * 50);
    p[1] = (char *)malloc(sizeof(char) * 50);
    p[2] = (char *)malloc(sizeof(char) * 50);
    p[3] = (char *)malloc(sizeof(char) * 50);
    p[4] = (char *)malloc(sizeof(char) * 50);
    p[5] = (char *)malloc(sizeof(char) * 50);
    p[6] = (char *)malloc(sizeof(char) * 50);
    // 多开辟一条空间,因为虽然只有6条字符串,但判断有没有第七条时,用到了新的空间(-> p[n][len] = *str)

    str_spit(str, p, &num);
    str_sort(p,num);
    my_print(p,num);
    
    int i;
    // 一定记得释放空间
    for (i = 0; i < 6;i++)
    {
        if (p[i] != NULL)
        {
            free(p[i]);
        }
        
    }
    if (p != NULL)
    {
        free(p);
    }
    
    system("pause");
}

--------------
查找子串数
在实际项目中,函数返回值一般用于表明函数执行状况
C没有异常处理机制,就是靠返回值来判断状况的
int sub_counts(const char *src, const char *sub)
{
    int n = 0;
    char *p;
    p = strstr(src, sub);
    while (1)
    {
        if (p == NULL)
        {
            break;
        }
        else
        {
            p += strlen(sub);
            n++;
        }
        p = strstr(p, sub);
    }

    return n;
}

void my_print(const int *arr,int len)
{
    int i = 0;
    for (i = 0; i < len; i++)
    {
        printf("%d\n",arr[i]);
    }
}


void main()
{
    char *str = "chjkadhga<ab>jhk<ab>kdh<ab>hdjsh<ab>hduha";
    char *sub = "ab";
    int arr[] = { 1, 2, 3, 4, 5, 6, 7 };

    printf("%d\n",sub_counts(str+11,sub));    // 指针变量做参数,传的是一个地址,只要是地址即可,不止是首地址
    my_print(arr+2,5);            // 后一点的地址也可以,操作后面的字符    
    printf("%s\n",str+5);    // str保存的只是首地址,可以跳过几个空间,输出后面的字符
    system("pause");            // 数组名只是一个地址,可以传后一点的地址
}                        // 灵活使用

---------------------------------------831
二维数组与指针


int n;
int *p=&n;
p+1;    // n(变量名)就代表那一块4字节的空间(整片空间),&n是取那一块空间的地址(首地址),p+1的步长就是n所代表的空间(移动4字节)

int a[10];// a(变量名)代表整片空间,&n是取这片空间的地址(返回一个地址,是指针,占4字节),同样&a也是一样的
&a+1,a+1,结果不同,前者前进了40字节,后者只前进了4字节
(a代表那一块40字节的空间,&a取空间(整个数组)的地址,&a+1步长40字节,a+1首元素地址,int类型,步长4字节)

int arr[2][3];
arr、*arr、&arr、&arr[0][0]、arr[0]    都是一样是第一个元素的地址(arr[0][0]),但含义不一样
    arr相当于二级指针,*arr获取的值,还是一个地址(arr[0][0]的地址)
arr[0],不是变量,是一个数组名称(一维),是二维数组第0行的首地址(一个数组(arr[0]的[0]、arr[0]的[1],就相当于一个数组)的首地址)
*(arr+1) ,代表arr[1][0]这个位置,依旧是一个地址,数组的首地址
    sizeof(arr[0])==12,一行所有元素占的字节,代表数组名称(一维),也是一个数组
*(arr+1)+2,代表arr[1][2]的地址

arr、代表整个二维数组的地址,数组(二维)名称
    sizeof(arr)== 24,整个二维数组占的字节
    sizeof(arr[0]) / sizeof(int),可得到有多少列
    sizeof(arr) / sizeof(arr[0]),可得到有多少行

&arr[1][0],
arr+1,代表这个二维数组的首地址,代表arr[1][0]这个位置了,向下移动一行(3个元素)
arr[1],代表该数组中,具体某一元素的地址,虽然值与 arr+1 一样,但含义(数据类型不一样)不一样
// 这三个值是一样的,但含义不一样

arr[1]+1,代表arr[1][1],移动了一个元素
&arr[0][0]+1,代表arr[0][1]的地址
int *p=arr;    // p指向一维数组,这时arr就相当于一维数组可以*(p+i),依次访问arr[0][0],arr[0][1],arr[0][2],arr[1][0],arr[1][1]元素
        // 因为数组地址是连续的,可依次访问

int w[] 相当于 int *p;    // 在函数中没法将形参sizeof(w)/4 得到数组的长度,所以还必须得有一个表示长度的形参
int w[][] 相当于 int (*p)[];
p++,//移动了4字节
(*p)++,// 移动了4字节


数组做函数参数会退化为指针
C编译器认为只需要知道那片空间的首地址即可,没必要把整个空间的数据都赋值进去
要知道这片空间的长度,再传一个参数进去
int fun(int w[]){}    //一维数组做形参可不写长度
int fun(int *p){}    // 退化成一级指针


void fun(int *p[5]){}
void fun(int **p){}// 数组(有[]的,都会退化)


int fun(int w[][5]){}    //二维数组做形参,第一维可不写,但第二维必须写,不然不知道一行多元素(不知道多少时换行)
            //(数组做参数会退化为指针,不可获原数组长度,第二维不写,不知道移动步长是多少)
            // 不退化,一个数组的数据都复制进去,太耗性能
int fun(int (*p)[5]){}    // 二维数组,最终退化为这种类型(数组指针)
//多维数组做函数参数,一般情况下,只能表达到二维,
//如果是三维内存(我们程序员起的名字),已经没有意义。
//一般情况:1级指针代表1维,二级指针代表二维。。。
//如果有超过char ***级及3级以上的指针,则不代表几维的内存
void fun(int ***p){}

char (*s)[10];// 数组指针(就是指向数组的指针,就是一个指针变量,指向的只是一个数组而已(该数组长度为10),不能指向数组长度为其他值 的数组)
        // 指向的是数组首元素地址,数组的首地址就是一个指针,s相当于一个二级指针
char (*s1)[10][10];    // 指向一个char c[10][10] 的指针
sizeof(s)==4;//指针就是占4字节

char str[10];
s=str;        // 相当于指向变量

char str1[20][10];
s=str1 相当于 s=str1[0];    // 相当于指向一个数组(长度为20),str1相当于是一个长度为20的数组(每个元素又是一个长度为10的数组)
str1[0]="sdfaf";
str1[1]="dfafxf";
s++ 相当于 str1[1][0]  // 移动到下一元素,(移动了10字节)


*(*(s+i)+j) 相当于atr1[i][j]
atr1[i][j] 在C语言内部,还是按*(*(s+i)+j) 来处理,a[i],与*(a+i) 意思一样,取某位置值的意思

const :定义变量,只能在定义时赋值,之后不能再修改了
const char *str;
char *pp;
pp = (char *)=str;    // 这样强转是没用的,编译器在处理时,会将str指向的空间拷贝一份,再让pp指向新空间
            // 修改pp的数据时,修改的是新空间的,str原空间的数据并不会修改

const int a;
int const a; // 两者一样,不能修改a的值
int *p=&a;
*p=400;    // 但是通过指针可以修改a的值

指向常量的指针
int i;
const int *p=&i;// 不可以通过 *p=100,来改i的值,只能用i=100 改i的值,但还可以指向其他变量
        // const 修饰指向的空间


指针常量
int *const p = &i;    // 该指针只能指向i,不能修改,不能指向其他变量,但可以*p=100,来改i的值
            // const 修饰p,

const int *const p = &i; // 都不能修改
指针变量 和 指针指向 的空间是两个不同的概念,看const在*号左边还是右边


const形参
int fun(const int[][4],const int **p){}    
// 在函数里面不能通过指针修改变量的值,数组的值(数组其实就是按指针处理的,即也不可修改)

指针可作为返回值
int *fun(){}


函数指针
一个函数在编译时,会分配一个入口地址,这个地址就是函数的指针,函数名就代表这个地址
函数地址就是代码区的地址
可以定义一个指针,指向该地址
void fun(int ){}
int ff(){}

void(*p)(int);// 定义一个指向函数的指针,(函数名本身就是一个地址,用一个指针变量代替)
int(*p1)();    // 指向的函数没有形参,定义该指针时也不用指定参数类型
void(*p2[10])(int);// 定义一个 函数指针 数组
void(*p2[10])(int)  = {fun1,fun2};    // 函数指针赋值

p = (  void(*)(int)  )fp;    // 函数指针类型转换(去掉函数名即可)

p=fun;// 指向函数
p(100);// 调用函数


函数指针作为形参
int add(int a,int b){}

int fun(int(*p)(int,int),int a,int b)    // 函数指针做形参
{
    _endthread();    // 结束当前线程
    return p(a,b);
}

fun(add,10,20);    //直接传函数名,

        //其实fun 叫 回调函数,动态决定调那个函数

#include <process.h> 里面开启一个线程的函数,其参数就是函数指针
void fun(void *p){}
HANDLE _beginthread(fun,0,NULL);    // 开启一个线程,线程里面执行fun函数,执行完,该线程就结束了
                // 参数1就是线程里面要执行的内容,该函数返回值必须是void,形参必须是void *
                // 参数2是给该线程指定的堆栈大小,0代表不具体限定,可大可小
                // 参数3是fun()方法的参数
                // 返回线程ID
WaitForSingleObject(pid,INFUNUTE);    // 等待指定线程执行完,再往下执行,参数2表示无限等待


函数指针 作为 函数返回值

int add(int a,int b){}
void *fun()
{
    return add;     // 返回函数,任何指针都可以 赋值给void *类型,一般不写具体函数指针的类型(太长了)
}
有了 函数指针 做返回值
生成dll时,有些函数可不导出来(保密起来),但可用导出来的函数,去调用未导出的函数,甚至可将未导出的函数返回 给其他程序用


-------------------------------------901
内存管理
作用域:
{}代表代码块,同一代码块不可有同名变量,变量的作用范围就在{}内,出了范围内存就被回收了
不同代码块内看有重名变量,使用时用距离自己最近的变量
int main()
{
    int a=100;
    
    {
        int a=10;
        printf("%d",a);    // 用的是里面的a,输出10
    }

    int n=10;
    while(n--)
    {
        int n=0;
        printf("%d",n);    // 输出10个 0
    }
}


自动变量:默认情况任何变量都是自动变量,即 自动分配内存
auto int a;

寄存器变量:不能取它的地址,它没有地址
register int a;

静态变量:
{
static int a=1;//在程序一开始加载内存的时候就有了,直到程序结束,不是执行到定义语句才有(保存在静态区)
        // 且在静态变量的内存地址不变
        // 只能在代码块内访问(但一直存在内存),初始化语句只执行一次,之后的定义语句不执行
static int a=10;     
static int a=99;    // 一个代码块内定义同名的变量是不行的

for(int i=0;i<10;i++)
{    
    static int a=0;    // 只执行一次初始化语句,之后不执行(内存中有了就不会执行了)
    a++;
    printf("%d",a);// 依次输出1,2,3,.....

}

}


-------------
void fun()
{
    static int a=0;
    a++;
    printf("%d",a);
}

int main()
{
    for(int i=0;i<10;i++)
    {
        fun();    // 在函数里面的静态变量也是一样的原理,程序一执行就有了,程序结束前不会回收
            // 输出 1,2,3......
    }
}

全局变量
不在任何函数内,不在任何代码块内,任何地方都可以访问
与静态变量一样,程序一开始就有了,直到程序结束

#include <stdio.h>
int n=0;    // 全局变量
int a;
int a;    //在C语言中,重复定义多个同名的全局变量是合法的,多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上
int main()
{

}

不同.c文件里面,不能有同名的全局变量
a.c 中定义全局变量 int n =1;
b.c 中定义全局变量 int n =2; // 是不允许的,(注意这里赋初值了,不赋初值表示申明,都是申明(没赋初值)时,才是定义)
为了避免这种冲突,在使用一个全局变量时,可不直接用变量,通过一个函数返回,如:
int getA(){return a;}
int getA2(){return a;}    // 这时函数名必须不能同名了

但是可以给 全局变量 加static,就可以有同名的静态全局变量了
这时的static表示,这个全局变量只在当前.c文件内有效

但当前文件定义的全局变量,另一个文件访问不到,要申明;
如:在a.c定义一个 全局变量 int n=10,
在b.c里面要访问:
#include <stdio.h>
int n; // 这里是申明一个变量,表示该变量在另外的.c文件里面有,这里只是申明,要引用
    //其实就是 extern int n;加个extern更好理解(避免二义性)
    //    (但加了extern,并不会定义变量,明确说明用外部的变量,当另外的.c文件也没定义时,不会自动定义,就会出变量未定义错误)
    // 当外部其他.c文件没有定义该n变量时,这里就是定义变量的意思,其他文件有,则用其他文件的
    // 注意不能 int n=99;不能赋初值,这样就不是申明的意思了,表示定义了
int main()
{
    n++;    // 这时,n == 11
    // 函数内定义了一个与全局变量同名的变量,在函数内使用时是使用函数内的变量,无法使用全局变量(C没有域作用符)
}

全局变量、静态变量会自动初始化为0;


函数 默认就是全局的,不同.c文件里面不能有同名的函数
static void fun(){},加了static就可以了,表示只在该文件内有效,其他文件可以定义同名的函数了
例如,在.h文件里面定义一个函数,在多个.c文件include进来(include是将文件内容直接复制进来),这样就会出现函数名同名异常
如果是静态函数则不会,因为虽然被include到多个.c文件(内容直接复制过去),但各自都是只在所在的.c文件内有效
(多个.c包含同一个.h,里面的静态函数没有互不影响)


内存四区
内存条就是一个整的空间,每一块内存都有一个地址,
栈和堆各占一部分,从某一个地址开始就是堆了

虚拟内存:把硬盘当内存用

1.代码区:存放程序当中所有的代码,程序代码指令(代码编译后的二进制指令)、常量字符串,变量(这里是标号,代表的空间在其他区)
(其实说白了,就是全部的函数名,函数地址就是这里面的地址)
    不可读写,只能执行,可用函数指针访问
    就好比 函数指针,只能执行函数,不能给其赋值(*p=10),不能读取(*p),只能执行(p(1,2);)
    void(*p)()=fun;
    *p=10;    // 不能写
    int a=*p;    // 不能读
    p();        // 只能执行


2.静态区:存放全局变量/常量,静态变量/常量(任何地方的static变量),常量("abc",相同的常量就一个,不会占两块内存空间(可设置编译器用两块地址))
    先定义的变量在地址小,后定义的地址大
    程序结束后由操作系统释放
    只可读不可写

常量区:字符串常量和其他常量的存储位置,程序结束后由操作系统释放

3.栈区:存放所有的自动变量,函数的形参,返回地址(函数返回值,就是将函数内的变量值赋值给这个地址,调用该函数处
                    要获取返回值,返回地址的值再赋值给另外内存地址
                            (赋值时返回值在寄存器中,所有不能取函数返回值地址))
    程序启动时可设置栈的大小(所有定义数组时,太大了不行),一般才几kb
    由编译器自动放、出栈,当自动变量超出作用域时,自动从栈中弹出
    进、出不能跳跃,一个一个进、出,要想底下的弹出 上面的必须先弹出
    先定义的变量在栈底,后定义的在上面(底下的内存地址大,上面的内存地址小)
    但数组是首地址小,往后地址大(即分配一个数组大小的空间后,首地址指向顶部)
    在C语言中,函数的参数是从右到左入栈,调用函数 进入函数了 函数的形参才入栈
    栈内存不存在 内存最小单元 问题,用了几个字节,就占几个字节

    int i;
    scanf("%d",&i);
    int w[i];    // 栈里面的空间必须编译时就确定大小,于是这样动态定义数组是不行的(C语言是不能这样的)
            // 运行时栈大小是改变不了的
    --------------
    char *fun()
    {
        char w[]="hello";
        return w;
    }
    char *p=fun();
    printf("%s",p);
    // 输出的是不确定的值,因为指针所指向的变量被回收了(空间被释放了)
    // p相当于野指针,指向的那个空间不知道被其他变量、程序赋予了什么值
    // 布局变量w在栈里面,出了作用范围就被回收
    --------------
    int fun()
    {
        int a=100;
        return a;
    }
    int n=fun();
    printf("%d",p);
    // 输出的是10
    // 因为定义n时候,n有自己的空间,可用来存数,fun的return时,就是将a的值复制到n的空间,a被回收了,但n保存了a的值
    --------------
    char *fun()
    {
        return "hello";
    }
    char *p=fun();
    printf("%s",p);// 输出hello,常量保存在 静态区,没被回收
    --------------

    --------------
    char *fun()
    {
        char *s=malloc(10);
        strcpy(s,"hello");    // 注意是复制(复值到堆区),不能字节赋值,不然"hello"在 静态去了
        return s;
    }
    char *p=fun();
    printf("%s",p);// 输出hello,hello在 堆区,没被回收
    free(p);    // 可将函数里面的堆空间释放(s、p指向的地址是一样的),不能释放栈变量的空间
    --------------    
传值(函数里面改变不了 传入变量的值,其实是函数调用时开启了另外的内存空间,定义了另为变量,该变量值就等于传入的值)
传址(可改变 传入的变量值,数组算传址,其实也另外定义了变量(指针变量),指向传入的地址,函数内直接修改地址内的值)

    void fun(char *s)
    {
        s=malloc(10);
        strcpy(s,"hello");
    }
    char *p=NULL;
    fun(p);
    printf("%s",p);
    // 输出NULL,因为p定义时指向NULL,调用fun时,传入的地址为NULL,
    // 函数fun定义变量s开辟空间,赋值为p的值,指向p的值,也是NULL,新开启的空间 地址给了s,但p的值还是NULL

    --------------
    void fun(char *s)
    {
        strcpy(s,"hello");
    }
    char *p=malloc(10);
    fun(p);
    printf("%s",p);    // 输出hello,因为函数里面新定义的s变量,就是指向p所指的地址,修改的就是那个地址的值
    --------------

    --------------
    void fun(char **s)
    {
        *s=malloc(10);
        strcpy(s,"hello");
    }
    char *p=NULL;
    fun(&p);
    printf("%s",p);    
    // 输出hello,因为传入了p变量的地址(变量一定义就有地址) 函数内给该地址开辟了空间,并赋值了(相当于给p指向了地址)
    --------------

4.堆区:是个大容器,程序可以任意在堆中放任何变量(Java不能操作堆、栈,由Java编译器决定),一般是几G
    栈太小了,处理数据可在堆里面,默认情况不会往堆里面放任何数据,且堆内存使用后要自己清理
    可以在堆中定义 动态数组,动态分配空间,malloc(),C++和Java的new,
    依次分配空间,地址是依次增大
    分配之后返回首地址,首地址小,往后大
    在堆里面分配内存:<stdlib.h>或者直接只包含<malloc.h>也行
        // malloc函数开启一个指定字节数的空间,并返回该空间的地址(方便使用)
        // malloc 返回值是void *,转换我自己定义的类型即可
        int *p = (int *)malloc(sizeof(int)*10);// p本身还是在栈里面,只是指向堆里面的地址
        *p=100;
        free(p);// 释放内存,不占用这个空间了,可给别人用了,
            // 该指针一定要分配空间了,才释放,不然将其他地址的空间释放了 是很危险的
            // 但是不能释放两次,因为释放之后那块空间不属于你的程序了,不能再操作,不能再释放了
        p = (int *)malloc(sizeof(int)*5);
        // 分配的内存不一定连续
        //如果前面不释放内存,这里又申请内存,且用同一指针记住地址
        // 之前的那40字节空间还占着,别人用不了,自己又没有指针指向那里了(自己也用不了了),这就内存泄露了
        // 自己不释放,程序结束了,操作系统可能会为你释放,重启电脑可释放
        sizeof(p) == 4;// 指针占4字节
    操作系统管理内存最小单元是32k(不同系统可能不同),即该进程只用来1k,系统也会给你32k
    操作系统有 内存页 的概念,一页默认是4k
    int *p = (int *)malloc(1024);
    //申请1k空间,其实操作系统也分配了32k,拿4k给你用,再malloc申请空间>4k 就拿8k给你,>8k 则12k.....
    //直到这32k用完,再再malloc申请空间时,操作系统又分配32k,又4k 4k的给


    malloc();申请的空间不会自动清理,之前内存保存的是什么值就是什么值,要自己清除里面的数据
    memset(p,0,10*4);// 将数组的值初始化为0,参数1数组,参数2要赋的值,参数3字节数
    memset(p+5,0,5*4);//     将后数组半截清理

    int *p=calloc(10,4);// 申请40字节的堆空间,会将该空间清0,不用自己来清理
            // 相当于是 一个长度为10的int数组,参数1是长度,参数2是单元字节,一共就是10*4=40字节

    char *p=malloc(40);
    p="0123456789"
    p=realloc(p,100);// 重新给指针分配空间,并返回新空间的地址,之前空间的值(之前那40字节的值不变)还会保留下来,
             // 参数1要重新分配空间的地址,参数2新空间的大小
    //其实操作系统就是看看之前地址后面够不够,够,则在后面追加60字节
    //不够,则去其他地方找一块100字节的空间,并将之前40字节的值复制过去,释放之前的40字节空间
    // 新多出的60字节空间没有清理(值是随机的,注意自己处理)

    p=realloc(p,5);// 如果重新分配小的空间,前面的值还会保留,但后面的释放了,可被别人用了
    // 这时去输出字符串,由于没有结束符\0,会一直输出,即使超出长度范围,即使是内存中其他变量的值,直到遇到0('\0')
    p=realloc(NULL,100);// 与 p=malloc(100),完全一样

    char *p = strdup("xxx");
    // 分配内存 并将字符串拷贝进去,返回开辟的空间地址(字符串的首地址)


    int *p,*pHead;
    pHead = p = malloc(sizeof(int) * 10)
    p++;
    free(p);// 这样是错误的,释放空间时,一定要传那块空间的首地址(因为它是从给定的地址起,释放固定长度的空间)
        (如果不是首地址,则加上固定长度,就跑到外面去了,把其他的空间释放了,不行的)
    free(pHead);// 这才是正确的
    
    程序中的地址看起来是连续的,但是在物理内存条中地址不一定是连续的
    操作系统会将物理内存地址 映射为 虚拟内存地址,输出变量的地址,就是输出虚拟地址
    所以程序中申请空间,地址是连续的(那是虚拟内存地址),但物理地址就不一定连续了
    
    物理内存的有内存页的概念,一页多少k
    内存页越大,内存使用率越低,因为程序只要1k内存,一页给一个内存页的大小的字节,但效率高,地址连续,减少寻址执行时间
    内存页越小,内存使用率越高,但效率低,经常要去寻址,查找可用内存,导致地址不一定连续了
    寻址是操作系统专门开启的线程,专门整理内存的(将物理内存整理,映射为 虚拟内存)

    程序运行时,可一下子申请很大内存,然后自己管理这些内存,自己在程序中控制内存使用,不用操作系统来管理内存

1.操作系统把硬盘代码load到内存
2.操作系统把c代码分成四个区
3.操作系统找打main函数入口执行


Java不可以在栈中分配空间(栈里面只保存一个引用,具体空间是new出来的,在堆里面),C可以自由分配(数组)
同一区域的内存,程序的变量地址可能连续
每一个线程都有一个自己的栈,一个进程只有一个堆
栈的最大尺寸是固定的,超出则引起栈溢出,用不用都有那么大的空间
内存初始化是个好习惯,除了全局变量和静态变量以外,其他变量C语言编译不会帮你初始化,只有自己通过显示的初始化。
释放内存也是个好习惯:
    if(p)    // 判断是在寄存器里面的,比读、写内存效率高,读又比写效率高,所以多一下判断不影响效率
    {
        free(p);
        p=NULL;    //置为空,不然p就是野指针了
    }
    
---------------------------903
结构体(struct)
struct student    // 定义结构体,没有权限限定,全是公有,不能有函数,没有继承
{
    int id;    // 定义里面的成员
    char name[10];
    // 里面所有变量,地址是连续的,
};

typedef struct  _std
{
}Std;    // 定义结构体后,马上取个别名


typedef struct
{
}Std;    // 定义结构体后,马上取个别名
    // 没有原始名字,只有别名


typedef struct MyStruct ST;    // 取别名
struct MyStruct
{
};


struct queue
{
};
typedef  struct queue Queue;// 取别名

struct _std
{
}t1,t2;    // 定义结构体后,马上定义变量


struct
{
}t1;    // 直接定义变量,不写类型(匿名结构体)
    // 里面成员不能有默认值,
    // 匿名结构体对象不能初始化

int main()
{
    struct student t0;    // 创建结构体变量,未初始化,里面的成员值是不确定的
    struct student t1={12};    // 未初始化的成员,值是不确定的
    struct student t2={12,"李四"};    // 创建,并初始化
    struct student t3={.id=12,.name="李四"};// 初始化方式2
    struct student t4={12,"李四",.id=25};// 初始化方式3,后面又把前面的id值 改了
    // C里面定义结构体变量,struct必须要写,C++可以不写

    int a=t1.id;    // 访问结构体变量的成员值
    t1.id=100;    // 赋值
    int size = sizeof(struct student);
    // int size = sizeof(t1);都是获取结构体占多少字节

    // 定义结构体数组,访问元素
    struct student ts[]={{12,"aa"},{12,"aa"},{12,"aa"},{12,"aa"}};
    int b = ts[1].id;

}

结构体的内存对齐模式
结构体在内存的大小是和 结构成员最长(变量类型所占字节最长,不是数组,是看 数据类型) 的那个元素相关的

struct men
{
    int age;
};// sizeof(struct men) == 4
----------
|int = 4 |
----------

--------------------------------------------------------------
struct men
{
    char name;
    int age;
};// sizeof(struct men) == 8,浪费3字节
//结构体内,占字节最长的数据类型 是int,4字节,内存分配单元为 4字节
//所以新增一个成员,给其分配4字节,如足够,则剩下的3字节浪费;如不够,再加4字节,未占用的又浪费
//(char 只占1字节,有3字节浪费,自己不用,别人又不可用)
----------------
|char = 1| | | |
----------------
|int = 4       |
----------------
-------------------------------------------------------------
struct men
{
    char name;
    char home;
    int age;
};// sizeof(struct men) == 8,浪费2字节
// name分配了4字节,还有3字节没用,分配home的时候,home只要1字节,发现前面还剩3字节,于是不分配新的空间
// 紧挨着name占1字节,后面还有2字节浪费
// 分配age时,要4字节,之前剩下的2字节不够,于是分配新的4字节,总共占8字节
-----------------------
|char = 1|char = 1| | |
-----------------------
|int = 4              |
-----------------------
-------------------------------------------------------------
struct men
{
    char name;
    int age;
    char home;
};// sizeof(struct men) == 12,浪费6字节
// 结构体中成员的地址是连续的,不会倒回去分配未占用的地址
-----------------------
|char = 1|   |   |    |
-----------------------
|int = 4              |
-----------------------
|char = 1|   |   |    |
-----------------------
-------------------------------------------------------------
struct men
{
    char name;
    char home;
    short age;
};// sizeof(struct men) == 4,没有浪费的空间,分配单元是2
-------------------
|char = 1|char = 1|
-------------------
|short = 2        |
-------------------
------------------------------------------------------------
struct men
{
    char name[101];
    int age;
};// sizeof(struct men) == 108,4字节 4字节的分配,101最后还剩下1字节,也要再分配4字节,浪费3字节
-----------------------------------------------------------
位域:指定元素占的字节数
struct men
{
    char name:2;// 指定该元素占2bit,不是2字节
    // 但是sizeof(men) == 1,因为 分配单元 是根据 数据类型,不是看指定的 bit数
    // 这里浪费了6 bit 空间,name最大值是3,赋值4的话,超出所能保存的范围了,取低两位 则为0
    char name:10;// 不行,不能给他指定超过类型大小的空间,容不下
};

struct men
{
    char a:2;
    char b:2; // sizeof(men) == 1,分配单元为1字节,给a分配空间时分配了1字节,但只占用2bit,
          // 给b分配空间时,b只要2bit,之前剩下的空间还够,于是紧挨着a的2位后面 分配2位
          // 只分配了1字节,且浪费4bit
};


struct men
{
};// 在C里面这样是不允许的,在C++里面允许(sizeof(struct men) == 1)

typedef struct xs_ev_t
{
    int         type;    // 消息类型
    
    char        buf[0];    // 消息的内容,长度0,表示只是一个占位符,不占内存
                        // (sizeof(xs_ev_t)的值只是type占的空间)
} xs_ev_t;


// 这时 成员变量buf就占10字节空间了
 xs_ev_t* ev = xs_malloc(sizeof(xs_ev_t) + 10);

这样比指针好,没有空间时不占内存(指针得占4字节),有空间时,直接指向这块空间(指针还得手动指向这块空间)

结构体嵌套
struct std
{
    char a;
    char b;
    int c;
    double d; // 占8字节

};// sizeof(std) == 16


typedef struct _stdv
{
    int e;
    std d;    //嵌套另外的结构体
    /*
    // stdv v;    // 不能嵌套自己,自定义数据类型(本质固定内存块大小的别名),这里类型还没定义完呢,不知道占多大内存
    stdv *p;    // 指针是可以的,(就是4字节,大小固定了)
    struct  _stdv *p1;    // _stdv,stdv定义都行
    */
}stdv;// sizeof(stdv) == 24,分配单元8字节,浪费6字节
// sizeof时,把std 追加在stdv的e成员内存地址后面(std属于一个数据类型,内存是一个整体),取结构体中占空间最大的数据类型来分配内存
--------------------------------
|e = 4|  a = 1 | b = 1  |   |  |
--------------------------------
|c = 4|      |      |     |    |
--------------------------------
|double = 8                    |
--------------------------------


结构体赋值

struct men
{
    int id;
    char name[10];
};


struct women
{
    int id;
    char *name;
};

struct men m1={12,"dhkajfh"}
struct men m2=m1;// 就是一个变量 建立了一个新的空间,并将m1空间的值复制给m2
m1.name="xzc";
        // m1、m2是两个独立的空间,互不影响,不会改变m2的name值(m2自己创建了一个数组,互不影响)
        //不像Java,建立的是一引用,指向的是同一空间
        相当于以下的复制
memcpy(&m2,&m1,sizeof(m1));// 将m1的值 复制 给 m2,参数3是指定复制的空间

struct women m1={12};
m1.name = malloc(10);
m1.name ="haha";
struct women m2=m1;    // 结构体中有指针,赋值得注意
strcpy(str,"xzc");    // 复制 赋值后m1、m2的name指向同一地址(因为只是结构体内的变量复制,指针指向的空间的数据不会复制),
            // m1修改name值,导致m2的name值也变化了
            // 浅拷贝 就是结构体内的指针指向的空间的数据没拷贝
            // 深拷贝 则指针指向的空间的数据也拷贝了,C没有这个功能,要自己去拷贝
        //注意不能 m1.name ="xzc";这是改变指针的指向了,不是修改指向的地址的值
printf("%s\n", m1.name);
printf("%s\n",m2.name);


char str[]="hello"
struct women m1={12,str}
struct women m2=m1;
// strcpy(str,"xzc"); // m1、m2都指向str,改变str的值导致m1.name、m2.name的值都变了
m1.name="xzc"; // 还是不会改变 m2的name值,
        //虽然m1、m2指向同一数组,但这样赋值是将m1.name指向另外的地址了,并没有改变str的值
printf(m2.name);


struct women m1={12,"hdajkd"};
struct women *p=&m1;    // 定义指针,指向m1
sizeof(p) == 4,// 依然是指针,占4字节
(*p).name;    // 访问指针值,不专业
p->name; // 也可这样访问,专业    
.号 ->号操作符的本质是寻址(在结构体的那一块空间内找到成员变量),通过距离大变量p地址的偏移量(每一个成员变量距离p都有一定的偏移量)多少
(偏移量其实就是前面所有成员变量所占的字节数),
找到具体的成员变量地址(因为变量名本身就是一块空间的标识),相当于指针移位
其实这样,就可以通过任意一个成员变量,获取到p 结构体变量的地址了(变量的地址减去偏移量,就是结构体变量的地址)

//结构体的定义
typedef struct _AdvTeacher
{
    char *name; //4
    char buf[32];  //32
    int age; //4
}Teacher ; 

void main2()
{
    int i = 0;
    Teacher * p = NULL;
    p = p - 1;    // 指针变量的值只是一个地址,地址也只是一个数,任意运算,只要不读写地址的内存()
    p = p - 2;
    p = p +2;
    p = p -p;

    i = (int) (&(p->age)); //i的值为36,(p是空指针值为0) .号 ->号就是寻址,成员变量的地址就是结构体变量地址加上偏移量(该成员变量占的空间)

    int i = 0;
    i = (int )&(((Teacher *)0)->age );    // 这样可获取每个成员变量距离结构体变量的偏移量,可以将一个整数转换为地址
    //&,没有读写内存,属于cpu的计算,不会出错
    // .号 ->号就是寻址,没有读写内存,不会出错
}

结构体占内存大,可用malloc分配堆内存
struct women *p=malloc(sizeof(struct women)); // 定义结构体指针,并分配堆内存
p->id=10;
p->name=malloc(10);    // 给结构体内的指针 开辟空间
strcpy(p->name,"xzc");    // 给开辟的空间赋值,注意 不能p->name="xzc",这样就指向别的地址了,之前开辟的空间泄露

free(p->name);
free(p);// 释放空间,必须将里面的指针指向的空间释放,才释放结构体变量空间
    // 不然里面指针指向的空间就泄露了,因为里面指针变量 被释放了,指向的地址找不到了
    // (结构体内没有指针,直接释放就可以了,整个结构体是一整块内存,捆绑一起开辟/释放)

void fun(struct men m){}
结构体做函数参数,结构体内的指针变量,在函数内修改值,导致外面原来的结构体变量内的指针成员值变化
其他类型的成员不影响(包括结构体里面的数组,因为结构体也是一个普通变量,
做参数,函数会在函数内另外建立一个新的空间(数组也新开辟了),并将传入的变量的值 复制过来 )
,(指针传入 直接修改地址了的值了)

结构体指针 做参数,函数只是建立一个结构体指针(占的空间,比结构体变量做参数少多了),并指向传入的地址,
在函数内直接操作、修改传入地址所保存的值,所以结构体指针 做参数 会使外部变量值修改


联合体(union)
联合union是一个能在同一个存储空间存储不同类型数据的类型。
它里面的成员公用用一个空间(该空间的大小就是最长那个类型的长度),sizeof()得到最长那个类型的长度
联合体虽然可以有多个成员,但同一时间只能存放其中一种。
union std
{
    int i;
    char c;
};// sizeof(union std) == 4,里面最长类型是int 4字节
union std var;
var.i = 0x12345678;    // 给这个空间赋值,内存中保存的是78 56 34 12 
printf("%d\n", var.i);  // 输出0x12345678的十进制数,int占4字节,读、写操作都取4字节
var.c = 0x44;        // c 又来修改该空间的值,该空间的值就是 78 56 34 44,c只占1字节,所以修改只改1字节的空间
printf("%d\n", var.c);  // 输出0x44代表的字符,char占1字节,读、写操作都取1字节,其他空间的数据不变
printf("%d\n", var.i);  // 输出0x44345678的十进制数,int占4字节,取该空间4字节的值(尽管前面被修改过)
printf("%p, %p, %p\n", &(var.c), &(var.i), &(var.d)); // 地址都是同一个
联合体内有指针时,一定要将指针的值处理完,不然所保存的地址被修改,地址找不回,导致内存泄露


枚举类型
就是限定取值范围
enum spectrum { red, yellow, green, blue, white, black };// 定义一个枚举类型 spectrum
enum spectrum clr;    // 定义一个枚举变量
clr = black;    // 取值 只能值括号里面的值
clr = 0 ;// 0就代表red,C语言不严谨,可以这样赋值
if (clr != red)

printf("%d",clr);
// 输出0,枚举的本质就是int型的常量(不可以 red = 10,这样赋值,是常量),默认是0,1,2,3......,依次递增
enum spectrum { red = 10, yellow=100, green, blue, white, black };
// 定义类型时,规定其值,green 之后的值是递增的,101(grean),102(blue)
enum spectrum { red = 10, yellow=9, green, blue, white, black };
// 没有指定值的枚举,就在前一个枚举的的基础上递增,green值为10,不用管前面的值是否重复

typedef
类型定义,创建新的类型,其实就是给一个已有的类型取一个别名(简短的别名方便使用)

typedef unsigned char MyByte;    // 定义一个(取一个别名)MyByte;
// 定义方式就是与之前定义变量一样,只是把变量名改成了 新的类型名
MyByte n;    // 新的类型定义变量是一样的,其实就是代表unsigned char类型
n=1;


与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值
typedef是编译器处理的,而不是预编译指令
typedef比#define更灵活

通过typedef定义函数指针,当 函数指针 做 返回值、形参时更灵活,#define就没那么灵活了
const char *getsubstr(const char *src, const char *str)
{
    return strstr(src, str);
}
typedef const char *(*NetType)(const char *, const char *);// 
NetType funp;// 定义一个 const char *getsubstr(const char *src, const char *str) 类型的函数指针
NetType funp[10];// 函数指针 数组

NetType fun(NetType p,int n){}
// 函数指针 做形参、返回值


--------------------------904
文件操作,磁盘保存文件分为ASCLL形式(文本形式,0表示结尾),非ASCLL形式(非文本形式,二进制形式,0也可以用来表示数据)
证书:就是网络身份证


步骤:
1.打开文件
2.读写文件
3.关闭文件


struct _iobuf
 {
char *_ptr;//当前缓冲区内容指针
int   _cnt;//缓冲区还有多少个字符
char *_base;//缓冲区的起始地址
int   _flag;//文件流的状态,是否错误或者结束
int   _file;//文件描述符
int   _charbuf;//双字节缓冲,缓冲2个字节
int   _bufsiz;//缓冲区大小
char *_tmpfname;//临时文件名
};
typede f struct _iobuf FILE;


// 标准输入输出就相当于一个文件,就是该结构体
void   mainaaaa123()
{
    printf("\n%c", *(stdin->_ptr));//取出缓冲区内容
    printf("\n%d", stdin->_cnt);//缓冲区还有多少个字符,还没输入,0个
    printf("\n");
   char  ch=    getchar();
   printf("\n");
   printf("\n%c", *(stdin->_ptr));//取出缓冲区内容
   printf("\n%d", stdin->_cnt);//缓冲区还有多少个字符
   ch = getchar();
   printf("\n");
   printf("\n%c", *(stdin->_ptr));//取出缓冲区内容
   printf("\n%d", stdin->_cnt);//缓冲区还有多少个字符
   printf("\n");
   putchar(ch);
   //stdout stdin不需要刷新,会及时更新,非缓冲
   //file  fflush,刷新缓冲区
   system("pause");

   //char str[100] = "12345"; sizeof(str)=100.strlen(str)=5

}


FILE *p = fopen("D:/xxx.txt","r");
// 打开文件,返回FILE类型的指针,打开失败返回NULL,成功,返回文件流(最小操作单位是 字节)
// 参数1要打开的文件名(默认在当前目录里面,其他地方加路径),参数2是打开模式
在VS里面编译执行,当前目录是在工程名里面(debug文件所在的目录),不在工程名/debug里面

以后统一用"/",少用"\",因为前者Linux,Windows通用

r,w,a,rb,wb,ab,r+,w+,a+,rb+,wb+,ab+

r 以只读方式打开文件,该文件必须存在。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
wb 以只写方式打开或新建一个二进制文件,只允许写数据。
   按照二进制方式写文件,自己写什么数据就什么数据,系统不会帮你转化
    如:\n,是换行,但在Windows里面会自动改为\r\n,用wb模式就不会改了,换不了行了
r+ 以可读/写 方式 打开文件,该文件必须存在。写:之前的内容不会清0,只是覆盖(r-replace),且从文件最开始处写
    (可以对文件进行读和写操作,但是写时,记得fflush()后才能读,接着当前位置读;读操作后可直接写)
rb+ 读/写打开一个二进制文件(可执行程序、wps文件、非txt文件),允许读写数据,文件必须存在。
w+ 以可读/写方式 打开文件,若文件存在则文件长度清为零(w-wipe),即该文件内容会消失。若文件不存在则建立该文件。
   (可以读和写)
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,
  如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留(EOF 符保留)
a+ 以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,
   如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)
   (可以对文件进行读和写操作,但是写时,记得fflush()后才能读,接着当前位置读;读操作后可直接写)
wb+    以读/写方式打开或新建一个二进制文件,允许读和写。
wt+    以读/写方式打开或新建一个文本文件,允许读和写。
at+    以读/写方式打开一个文本文件,允许读或在文本末追加数据。
ab+    以读/写方式打开一个二进制文件,允许读或在文件末追加数据。
"t"           文本文件(默认项,默认不写b,就是文本文件)

二进制和文本模式的区别
1.在windows系统中,文本模式下,文件以"\r\n"代表换行。若以文本模式打开文件,并用fputs等函数写入换行符"\n"时,
函数会自动在"\n"前面加上"\r"。即实际写入文件的是"\r\n"(多了一个字节),但读取时 '\r'系统会自动去掉
2.在类Unix/Linux系统中文本模式下,文件以"\n"代表换行。
所以Linux系统中在文本模式和二进制模式下并无区别。

“b”只对windows有效,对于unix来讲是无效(不分文本文件还是二进制文件,当然加个b也没关系),

对于GBK编码的汉字,一个汉字两个字节,对于utf8来讲一个汉字3个字节,但如果英文字母都是一个字节


char c;
while((c=getc(p)) != EOF)// 读取一个字符(读到文件尾而无数据时便返回EOF,但只是文本文件才返回EOF),一个字节
{
    printf("%c",c);
}
EOF(就是-1),表示文件结尾,但不是文件里面的字符,是流的一种状态,不可见的,系统自动加上的,不用自己加
文件里面的任何内容都可读出来(空格、换行等等)

fclose(p);// 关闭文件


while((c=getc(p)) != EOF)// 读取一个字符,一个字节
{
    putc(c,p1);    // 向p1(文件指针)文件写一个字符
    //putc(c+10,p1);    // 加密,每一个字符的值+10,实现加密效果
}

char buf[1024] = {0};
while(!feof(p1)) // 判断是否到文件结尾
{
    //feof的特点是当文件读写指针读到最后的时候feof并不返回真
    // 要在调用读的方法,读一次,feof()就返回真了
    // 当然到文件最后了,没得内容了,读不到内容了,再读一次不会改变buf的内容
    // 其实是,文件内容的大小刚好为buf大小的整数倍时,最后一次刚好读完内容,不知道后面有不有内容了,需要再读一次
    //    如果最后读取的数据大小,小于buf的大小,则不用再读一次了,已经知道读完了
    //    feof函数的功能是检查一个stream中是否有结束标志
    //  fgetc(),用feof()判断结尾,会多读一次,这一次没读到数据,返回EOF

    memset(buf,0,1024);    // 避免最后读取的那段内容被写两次,读之前初始化 为 \0
      // 从p1读取一行内容,
                // 参数1存储读取数据的数组,
                // 参数2是参数1存储空间大小(可存多少字节,
                //    不一定度满,遇到文件结束、换行,就不读了,都没遇到,则填满空间,等下次调用)
                // 参数3要读取的文件
                // 返回值 与 参数1 一样,是读取的内容(指向读取到内容的指针),没读取新的内容就返回NULL,读完了
                // 空格、换行一并读进去,文件结束符不会读进去,换行了表示一行读完了
                // 读取一行后,会在数组里面放一个 \0,表示字符串结尾(所有buf的大小要多准备1字节)
                // (所以数组长度要记得多一字节,参数2也要记得 \0占的 字节,写2,其实只读了一个字符(自动追加了\0))

    strlen(buf);    // 可获取读了多少字符
    fputs(buf,p2);// 向p2写一行内容,空格、换行可写进去,遇到\0 结束(之后的内容写不进去)
              // 没遇到,则写完整个数组,如是指针,则一直写,直到 遇到 \0
              // 写成功返回 0 
}


fprintf(p,"%s%d","addafa",100); // 与printf一样的用法,只不过是向文件写了
                // 自动将 整数 转换为 字符串
                // 返回写了几个字符

char buf[10]
fscanf(stdin,"%s",buf); 从键盘输入
fprintf(stdout,"%s",buf);输出到屏幕
while(!feof(p))
{
    char buf[1024] = {0};
    fscanf(p,"%s",buf);
    printf("%s\n",buf);
    // 从p文件里面读一行内容 到buf,(这里是读取一行,读到空格、换行表示一行结束,空格、换行不会读进来),自动加 \0
    // 避免最后一段内容输出两次,每次先清理buf
    // 返回值是读取的 %..., [argument...]的个数
    // 也可以while (fscanf(p, "%s", buf) != EOF),判断是否读到结尾,避免多读一次
}
int n
fscanf(p,"%d",n);    // 与scanf()函数使用方法相同,
            // 从p文件里面读取一个整数(自动将字符串转为整数类型)

写文件时,不加换行,就是一直在一行写内容
加'\n',表示换行,但在Windows系统,会理解成'\r\n',自动加个 \r,用wb模式写,就换不了行了

#include <assert.h>
void assert( int expression );
assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,
    然后通过调用 abort 来终止程序运行

已放弃使用assert()的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
在调试结束后,可以通过在包含#include <assert.h>的语句之前插入 #define NDEBUG 来禁用assert调用,示例代码如下:
#include <stdio.h>
#define NDEBUG
#include <assert.h>

<sys/stat.h>
struct stat st;// 该结构体是sys/stat.h里面的
memset(&st,0,sizeof(st));
stat(p,&st);    // 获取文件的一些元数据,文件大小(字节,写了几个字节,这里就多少)、修改时间等等
        // 参数1是char *类型文件路径指针,参数2是保存文件元数据结构体
char *p=malloc(st.st_size * sizeof(char));// 知道文件多大了,就动态开辟一个那个大的空间


fseek(p,2,SEEK_SET); // 偏移2字节,成功返回0,失败 非0(并不可靠,到尾了往后移返回0,到头了往前移返回-1)
// 如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。
// SEEK_SET:从文件最开始处(第一个字符前 0位置,第二个字符前 1位置(读过了一个字节))偏移,正数 向前(右),负数 向后
// SEEK_CUR:表示从当前位子偏移
// SEEK_END:表示用文件结尾处(最后一个字符后)偏移


fseek函数和lseek函数类似,但lseek返回的是一个off_t数值,而fseek返回的是一个整型。
文件被打开时,偏移量会被初始化为 0,除非使用了 O_APPEND 。

如果参数 fd(文件描述符)指定的是 pipe(管道)、FIFO 或者 socket,lseek 返回 -1 并且置 errno 为 ESPIPE。
对于普通文件(regular file),返回值是一个非负整数。但对于特殊设备,返回值有可能是负数。
因此,我们不能简单地测试 lseek 的返回值是否小于 0 来判断 lseek 成功与否,
而应该测试 lseek 的返回值是否等于 -1 来判断 lseek 成功与否。

EBADF: fildes不是一个打开的文件描述符。
ESPIPE:文件描述符被分配到一个管道、套接字或FIFO。
EINVAL:whence取值不当

// 刚开始索引在第一个字节前,移动5字节,索引为4,
lseek(fd,5,SEEK_SET);
write(fd,"",1);// 写一个字节撑大文件,撑大后,索引为5,文件就有6字节大小了
如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”
没有被实际写入文件的所有字节由填充0


char c = getc(p);

ftell(p); // 获取当前位置(单位:字节)

fgetpos,fsetpos函数
fseek与ftell返回的是long类型,如果文件很大,超过long的范围,那么该函数会有问题
fgetpos与fsetpos函数可以处理更大的文件类型

fpos_t ps = 0;
fgetpos(fp, &ps);    // 获取位置 保存到 fpos_t 类型的ps变量里面
rewind(fp);    // 回到文件开头

fflush(p);
//其实所有的C库函数文件读写操作都是针对缓冲区进行的,而不是真实的文件.
// 先将读取的内容缓存起来
    1.当缓冲区满了,系统就会将数据一次性写入磁盘
    2.明确调用fclose(p)、fflush(p)将数据写入磁盘
    减少磁盘读写次数,提高效率,延长磁盘寿命


fprintf(p, "%s\n", buf);
fflush(p);//每次写到缓冲区的数据,都实时的更新写入到磁盘

二进制文件操作,效率高,可靠,就是数据排放 保存起来而已

rb、wb ,二进制文件 读写,磁盘中010101数据是怎样的,读取到内存就是怎样,内存 与 磁盘数据一样的
二进制方式只能用:
fread();
fwrite();
注意:这个函数以二进制形式对文件进行操作,不局限于文本文件(任何文件都可以二进制形式读、写,是最可靠的)
返回值:返回实际写入或读取的数据块数目
只要读取到文件最后,没有完整的读取一个数据块出来(即 达不到一个完整的单元大小),fread就返回0
多字节表示的字符,不读完整,会显示不全

第一个参数代表void *,写入或者读取的缓冲区(void表示可以是读写任何数据类型)
第二个参数是代表写入或读取的时候一个单位的大小,如 int类型,4字节,读、写一个数就占4字节
第三个参数是代表写入或读取几个单位,总的要读、写字节 为 单位大小*单位数
第四个参数是FILE *

char buf[]="hello";
fwrite(buf,1,strlen(buf),p);    // 表示要将一个 字符串 写入二进制文件,自己指定要写入的字节数
                // 将字符的ASCLL码 用二进制保存起来
                // 返回值为 具体写了多少个单元的数据,如不等于 要写入的数据单元数,则写入出错    

int buf[10]={0,1,2,3,4,5,6,7,8,9};
fwrite(buf,sizeof(int),10,p);    //写入 10 个 整数,每个整数占4字节,不是文本类型,而是直接将数的二进制写入

int a=10;
fwrite(&a,sizeof(int),1,p); // 写一个数,在磁盘里占4字节

// 总之,就是将内存里面的数据,原封不动的写到磁盘中


FILE *p = fopen("xzc.txt","rb");
char c;
while(!feof(p))
{
    //feof的特点是当文件读写指针读到最后的时候feof并不返回真,
    // 要在调用读的方法,读一次,feof()就返回真了
    // 当然到文件最后了,没得内容了,读不到内容了,再读一次不会改变buf的内容
    // 其实是,文件内容的大小刚好为buf大小的整数倍时,最后一次刚好读完内容,不知道后面有不有内容了,需要再读一次
    //    如果最后读取的数据大小,小于buf的大小,则不用再读一次了,已经知道读完了
    //    feof函数的功能是检查一个stream中是否有结束标志
    
    fread(&c,1,1,p);
    printf("%d\n",c);
}
fclose(p);

while(fread(&c,1,1,p) >0 )    // 避免多读的那一次,导致最后那一段内容写两次
{
    printf("%d\n",c);
}

char w[1024];
while(!feof(p1))
{
    fread(w,sizeof(w),1,p1);// 从p1读出来,写入p2
            //不一定读满,文件读完了,就不读了,或者填满空间了,不读了,等下次调用)
    fwrite(w,1,sizeof(w),p2);// 这里是二进制数据了,读出来是不会自动加 \0,本来没有1024字节,硬要写1024字节,导致内容错乱
}

char w[1024];
while(!feof(p1))
{
    int n = fread(w,1,sizeof(w),p1);// 一个字节为单位,方便计算读了多少字节,因为达不到一个完整的单元大小,fread就返回0
    fwrite(w,1,n,p2);    // 读几个字节,写几个字节
}


结构体的读、写,就是数据排放 保存起来而已
struct men
{
    int id;
    char name[20];
};
struct men st[10] = { {1, "刘德华"}, {2, "张学友"}, {3, "苍老师"}, {4,"周杰伦"}, 
    {5, "武老师"}, {6, "饭岛老师"}, {7, "成龙"}, {8, "陈冠希"}, {9, "李天一"}, 
    {10, "小泉"} };

FILE *p=fopen("a.txt","wb");
if (p == NULL)    // 打开失败
{
    return 0;
}
fwrite(st,sizeof(struct men),10,p);
// 写入 10个 结构体变量
// 单元大小就是结构体类型的大小
fclose(p);


读结构体
FILE *p1=fopen("a.txt","rb");
if(p1==NULL)
{
    return 0;
}
struct men m;
memset(&m,0,sizeof(struct men)); // 清零 结构体 变量
while(fread(&m,sizeof(struct men),1,p1) > 0)    // 不知道有多少个结构体,所以一个一个读,全部读出来
{
    int id=m.id;
}


fseek(p1,sizeof(struct men)*2,SEEK_SET);
fread(&m,sizeof(struct men),1,p1); // 偏移指针,读取第3个结构体,(苍老师)

<time.h>

clock_t t = clock(); // 获取系统当前时间,单位:毫秒


//实现流程
//循环读每一行,检查key配置项是否存在 若存在修改对应value值
//若不存在,在文件末尾 添加 "key = value"
//难点:如何修改文件流中的值

int SetCfgItem(char *pFileName /*in*/, char *pKey /*in*/, char * pValue/*in*/, int ValueLen /*in*/)
{
    int        rv = 0, iTag = 0, length = 0;
    FILE    *fp = NULL;
    char    lineBuf[LineMaxLen];
    char    *pTmp = NULL, *pBegin = NULL, *pEnd = NULL;
    char    filebuf[1024*8] = {0};
    
    if (pFileName==NULL || pKey==NULL || pValue==NULL) 
    {
        rv = -1;
        printf("SetCfgItem() err. param err \n");
        goto End;
    }
    
    fp = fopen(pFileName, "r+");
    if (fp == NULL)
    {
        rv = -2;
        printf("fopen() err. \n");
        //goto End;
    }

    if (fp == NULL)
    {
        fp = fopen(pFileName, "w+t");
        if (fp == NULL)
        {
            rv = -3;
            printf("fopen() err. \n");
            goto End;
        }
    }
    
    fseek(fp, 0L, SEEK_END); 
    //获取文件长度;
    length = ftell(fp);

    fseek(fp, 0L, SEEK_SET);
    
    if (length > 1024*8) 
    {
        rv = -3;
        printf("文件超过1024*8, nunsupport");
        goto End;
    }
    
    while (!feof(fp))
    {
        //读每一行
        memset(lineBuf, 0, sizeof(lineBuf));
        pTmp = fgets(lineBuf, LineMaxLen, fp);
        if (pTmp == NULL) 
        {
            break;
        }
    
        //key关键字是否在本行
        pTmp = strstr(lineBuf, pKey);
        if (pTmp == NULL)
        {
            strcat(filebuf, lineBuf);
            continue;
        }
        else
        {
            sprintf(lineBuf, "%s = %s\n", pKey, pValue);
            strcat(filebuf, lineBuf);
            //若存在key
            iTag = 1; 
        }
    }

    //若不存在 追加
    if (iTag == 0) 
    {
        fprintf(fp, "%s = %s\n", pKey, pValue);
    }
    else //若存在
    {
        if (fp != NULL) 
        { 
            fclose(fp); 
            fp = NULL; //避免野指针
        }

        fp = fopen(pFileName, "w+t");
        if (fp == NULL)
        {
            rv = -4;
            printf("fopen() err. \n");
            goto End;
        }
        fputs(filebuf, fp);    // 只能把文件的内容全部读完出来,修改,关闭文件,再打开文件,保存数据
        //fwrite(filebuf, sizeof(char), strlen(filebuf), fp);
    }
    
End:
    if (fp != NULL)
    {
        fclose(fp); 
    }
    return rv;
}

-----------------Linux,FIleZilla(传文件的软件,注意传输类型,不然会失真,最后都二进制传输:传输-手动传输-传输类型),SecureCRT(终端软件)
make
Linux下创建、编译项目的工具,依赖makefile(Makefile 也行)文件,在里面写构建指令
也可以自己指定文件,make -f xxx(默认用当前目录下的makefile)
也可以自己写一个脚本,赋予执行权限,执行文件即可

makefile基本结构:目标、依赖、命令三部分组成

TARGET … : DEPENDENCIES …
        COMMAND
        …
目标(TARGET)程序产生的文件,如可执行文件和目标文件;
    目标也可以是要执行的动作,如clean,也称为伪目标。
依赖(DEPENDENCIES)是用来产生目标的输入文件列表,一个目标通常依赖于多个文件。
命令(COMMAND)是make执行的动作(命令是shell命令或是可在shell下执行的程序)
    注意:每个命令行的起始字符必须为TAB字符!

如果DEPENDENCIES中有一个或多个文件更新的话,COMMAND就要执行,这就是Makefile最核心的内容
Make工具的最核心思想:如果DEPENDENCIES有所更新,需要重新生成TARGET;进而执行COMMAND命令


#表示注释
-------------------
start:    // start是一个伪目标
    gcc xxx.c    // 前面必须是tab空格
#这是注释
--------------------
start: main.o a.o    // 依赖,表示执行start目标指令时,看看main.o a.o文件存不存在,存在才执行,不存在则执行main.o a.o目标指令
            // 就是 依赖与 main.o a.o标签的指令,有一步出错就不会往下执行了
    gcc -o app a.o mian.o
    echo "---编译成功---"    // 在命令行打印信息,但默认执行指令会将里面的指令内容打印出来
    @echo "---编译成功---"    //隐藏指令,只打印"---编译成功---",不会将指令输出echo "---编译成功---"
main.o a.o:
    gcc -o main.o -c main.c
    fcc -o a.o -c a.c
clean:
    rm -rf *.o
---------------------
main:main.o sub.o add.o
    gcc -g -Wall main.o sub.o add.o -o main

main.o:main.c                // 依赖的源代码发送变化了,将重新编译
    gcc -g -Wall -c main.c -o main.o
sub.o:sub.c sub.h
    gcc -g -Wall -c sub.c -o sub.o

add.o:add.c add.h
    gcc -g -Wall -c add.c -o add.o

clean:
    rm -f *.o


-------------
直接 make命令,只执行第一个目标的指令
make clean ,指定执行那个目标的指令

SRC=main.o
SRC=main.o a.o b.o    // 值任意,这里只是表示多个文件
N=gcc    // 定义一个变量,一般大写,并赋值,没有类型
start: $(SRC)
    $(N) -o app a.c        // 使用N变量
$(SRC):        // 标签、依赖都可以用变量,任何地方都可以用变量代替
    $(N) -o mian.o -c mian.c


SRC=main.c
OUT=$(SRC:.c=.o),意思是将SRCS变量中的.c替换为.o,再给OUT变量
start:
    gcc -o $(OUT) $(SRC)

sub.o:sub.c sub.h
    gcc -c sub.c -o sub.o
-->
sub.o:sub.c sub.h
    gcc -c $< -o $@    // $<: 代表依赖列表的第一个依赖文件,sub.c sub.h依赖中的sub.c
            // $@: 代表目标,即就是TAGET名(sub.o)
-->
sub.o:hh.c gg.c
    gcc -c $^ -o $@    // $^ :代表依赖列表的所有文件 a.c b.c

%.o:%.c
    gcc -c %< -o %@        // %.o:%.c    模式规则 表示 将所有的.c文件作为依赖,所有的.o文件作为目标,.c到 .o文件名一样的
-->
.c.o:
    gcc -c %< -o %@     // .c.o:    后缀规则 其实就是%.o:%.c 一样的功能

make的自动推导
start:a.out b.out        // 该目标 没有指令
a.out:a.o
    gcc -o$@ $<        // make工具发现没有a.o,便自动编译a.c生成a.o(根据文件名a,查找同名的文件a.c)
b.out:b.o
    gcc -o$@ $<

// 甚至可只写这一句,make工具会,自动编译 .c文件,再编译.o,最终生成.out文件
start:a.out b.out


.SUFFIXES:.c .o        // 表示任何.c文件 与 .o文件关联,用.c.o时,建议加上该规则(即使不加可以,还是得加上)

SRC=main.c\    // \表示又多个文件,编译多个文件
    a.c\
    b.c
OUT=$(SRC:.c=.o)    // 同样所有的文件都替换
EXE=hello
start:$(OUT)
    gcc -o $(EXE) $(OUT)
.c.o:
    gcc -o $@ -c $<    
                // 这里一写,不管又几个源文件,都可以编译,不用多个语句一个一个编译
                // 只需要修改SRC变量的值即可

/*******工程项目完整的makefile文件***********/


.PHONY:clean

WORKDIR=.

VPATH = ./src            // 系统的一个变量,表示去哪里找要编译的.c文件,指明值即可


LIBOBJS=  socketclient.o itcastlog.o
OBJS= demo01_testplatform.o

LIB1 = libmysocket.so
EXE1 = mysocketexe
CC = gcc
CFLGS= -Wall -g -I$(WORKDIR)/inc/
LIBFLAG = -L$(HOME)/lib

all: $(LIB1) $(EXE1)


$(LIB1):$(LIBOBJS)
    $(CC) -shared -fPIC $^ -o $@ 
    cp  $(LIB1) $(HOME)/lib
    mv  $(LIB1) ./lib
    

$(EXE1):$(OBJS)
    $(CC)  $^ $(LIBFLAG) -lmysocket  -o $@


.c.o:
    $(CC) $(CFLGS) -c $< -o $@  -shared -fPIC
clean:
    rm -rf $(BIN) *.o $(HOME)/lib/$(LIB1) ./$(LIB1) 


/*******工程项目完整的makefile文件***********/


常见的make出错信息:
No rule to make target ‘target’.Stop
makefile中没有包含创建指定的target(标签),而且也没有默认规则可用

‘target’ is up to date 
指定的target相关文件没有变化。

command:Command not found
make找不到命令,通常是因为命令被拼写错误或者不在$PATH路径下。

// 而且编译过后,没修改的源文件不会再编译了,修改了才会编译
// 是比较.c .o文件的最后修改时间来 决定是否要编译

qmake
QT creator的指令,直接 qmake后 会生成Makefile文件,再直接 make 指令,就开始构建项目了 
(在项目的目录里面 执行 指令)

---------------------
gdb
安装 gdb-7.2-56.el6.i686.rpm

调试程序、查看调试信息,发现哪里出错、抛异常了
【1】
gcc -g -o a.o -c a.c        // -g 表示可调试执行(加调试信息),必须在编译的时候加-g,必须加了-g,gdb工具才可调试
                // 程序崩了后,会将错误日志信息,写入core文件
gcc -Wall -g -o a.o -c a.c    // 打开所有的警告信息,make 构建时就会显示警告信息
gcc -Werror -g -o a.o -c a.c    // 把所有警告当 error处理,编译时有警告也停止编译
                (一些无影响的警告不会停止,一些潜在的警告会停止下来)
-w:关闭所有警告

编译后执行程序
a.out    // 如果程序出错,会在程序程序所在的目录生成 core文件(保存一些调试信息,运行日志)
    // 如果没有生成core文件要,设置生成core文件(执行一下命令),再执行程序,再崩一次就有日志信息了
    // 运行一次异常,core多一个

【这样设置只是本次开机有效,重启之后又得设置】
ulimit -c 3000    // 设置生成core文件,3000表示文件的大小,其实就是core.300后面的文字就表示文件大小
或者
ulimit -c unlimited    // 表示不限制文件的大小
或者
ulimit -c    // 不写也行

【注意】程序出错时,系统默认不会生成core文件【设置配置,永久有效】
1.Linux默认是不生成core文件的,所以需要在用户profile文件中添加 ulimit -c unlimited
修改完成之后让profile文件及时生效
2.单个用户修改:
    在宿主目录下修改.bashrc,在最后添加 ulimit -c unlimited
    . .bashrc生效即可

【2】
// 启动gdb,查看core的内容,如果core后有数字,也要输入数字,core.4564
gdb a.out core    // 进入gdb了,a.out:运行的程序名,core:要查看的core文件
gdb -q hello core    // -q    关闭gdb软件信息
where    // 查看那里出错
quit    // 退出查看

【3】直接调试运行程序
gdb a.out // 进入调试模式 运行程序,gdb 加载a.out程序了

run  // 运行程序,如果出错,执行完了,输入run,可以再执行一次
where // 查看那里出错
list // 还看不懂,用list查看出错的代码块

break 48    // 在48行设置断点,main函数运行到48行时会停下来
break add    // 给add方法加断点
break main.c:30
break main.c:add    // 指定文件内设断点
delete 48  // 删除某一断点,数字是断点的编号(info break可查看),不是断点所在的行
run    // 在运行前设置断点,该命令未执行前,程序还没跑
print n    // 查看停下来后,查看变量n的值
print *p    // 查看指针的指向空间的值
print n+10    // 查看表达式的值
print main.c::n        // 输出指定文件内变量的值
whatis n  // 查看n变量的数据类型
set variable n = 100  // 代码停下来时,改变一个变量n的值,
list // 可查看程序停留在断点处的代码
list m,n   //m,n是要显示包含错误首次出现位置的起始行和结尾行。不带参数的list将显示附近的10行代码break main.c:100    // 有多个文件时,可指定在那个文件加断点
info break    // 查看所有的断定信息,可查看未运行到的断点,运行过的断点
continue // 继续执行,停留在断点处不执行了,用这个命令继续执行,不停留,直到遇到另一个断点,或者程序结束
step //当遇到一个函数的时候,step将进入函数,简写s,每次执行一条语句,相当于step into,一下执行一条语句停下来
next //当遇到一个函数的时候,next将执行整个函数,简写n,相当于step over,一下子执行完函数 再停下来,再一行一行执行
// 不停的回车,代表执行上一条命令
return // 直接返回函数,未执行的代码也不执行了,return 0,可加返回值

同样可以用tab键补全命令
help all:查看怎么使用

------------------
跨平台技术

gcc -pedantic -o a.o -c a.c    // -pedantic,发现一些不符合 ANSI/ISO C标准的代码 (如注释 是/**/,不是//)

gcc -O -o a.o -c a.c    // -O,表示优化代码,不要与-g一起用

gcc -DLINUX -o a.o -c a.c  //-D,表示定义一个宏 LINUX,相当于#define LINUX

gcc -static -o a.out a.o // -static,表示连接时,如果同时有静态库、动态库,强制链接时链接静态库
            // 默认是连接动态库


#include <stdlib.h>

#ifdef LINUX
#include <unistd.h>
#else
#include <Windows.h>
#endif

int main()
{
    while(1)
    {
    printf("hello\n");
#ifdef LINUX
    sleep(1);        // 根据宏,选择不同的代码执行
#else
    Sleep(1000);
#endif
    }
    return 0;

}

gcc -DLINUX -o a.o -c a.c  // 编译时,定义宏,达到跨平台效果
gcc -o a.out a.o


c++程序,用g++命令,参数与gcc一样的

电子词典
数组版、数组两次读文件版、链表版:将工程的dict.txt文件拖到exe上执行即可
Linux版(编码的转化)、MFC图形界面版(编码的转化)、QT图形界面版(编码的转化)

dict.txt是gbk编码

Linux版:
Linux默认是按utf8编码处理文件,要将读入的gbk内容转化为utf8格式
<iconv.h>
int gbk2utf8(char *src, size_t *srclen, char *dest, size_t *destlen)
{
    iconv_t cd = iconv_open("UTF8", "GBK"); //源字符串为GBK,目标UTF8
    if (cd == (iconv_t) - 1)    // 返回值为-1表示不能转化
    {
        printf("open iconv error %s\n", strerror(errno));
        return -1;
    }

    size_t rc = iconv(cd, &src, srclen, &dest, destlen); //将src字符串转化为目标dest,src要转化的字符串,srclen字符串长度
    if (rc == (size_t) - 1)                    // dest存转化结果的数组,destlen数组的长度
    {
        printf("iconv error %s\n", strerror(errno));
        return -1;
    }
    iconv_close(cd);    // 用完记得关闭,打开只能用一次,还要用,再打开
    return 0;
}


MFC版
默认是按utf8编码处理文件
在.rc文件里面添加控件,右击控件->添加类变量(就可以通过类变量操控 控件了)

CStringA cs(edit1);//把edit1从unicode转化为char * (gbk编码),因为从控件获取的内容是UTF8编码的
//edit1.GetBuffer());//得到CString里面藏那个const char *

QT版
<QTextCodec>
void Widget::on_pushButton_clicked()
{
    QTextCodec *codec = QTextCodec::codecForName("GBK"); // 要将GBK格式转换为其他格式
    char content[1024] = {0};

    //codec->fromUnicode(ui->lineEdit->text());//将edit1的内容转化为GBK
    if (search_dict(p, dict_size, codec->fromUnicode(ui->lineEdit->text()).data(), content))//根据用户输入,在字典中检索
    {
        //codec->toUnicode(content);//把content从GBK编码转化为UTF8
        ui->label->setText(codec->toUnicode(content));
    } else
    {
        ui->label->setText("not found");
    }
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值