C基础加强

参考资料:b站Av19301027

  • 数组名字作为参数
    数组作为函数参数,退化为一个指针
    void fun(int a[6]) 其中6可以是任何正整数或者直接不写,没有任何意义,因为这里只是将a作为一个数组起始地址(不管是多长的数组),然后退化为一个指针。
  • 数组名字
    首先它是代表整个数组的,所以&数组名字会得到数组的地址,&数组+1会在数组的基础上增加整个数组内存长度,这个时候数组名字跟int a; 中的a是一样的,是一个变量名字,只不过是一个数组的名字,不是地址,sizeof(数组名字)得到数组大小也是这个道理,另外数组名字是个常量,不能buff=0x1111;接下来讲一个特例,就是单独打印a的时候,a代表数组第一个元素的地址,数组名字+1也是这样的。
#include #include using namespace std; int main(int argv, char** argc) { int a[10] = { 0,1,2,3,4,5,6,7,8,9 }; cout << "a:" << a << endl; cout<< "&a" << &a << endl; cout << "a+1:" << a+1 << endl; cout << "&a+1" << &a+1 << endl; system("pause"); }

  • 定义数组类型——数组也是一种数据类型
typedef char A[10]; A buff; //buff相当于char buff[10];

  • 二维数组
    char a[3];
    char aa[3][30];
    将二维数组看作一维数组的数组,一维数组是基本类型的数组,因此a表示首元素(char)的地址(char * ),a+1的步长为1,aa也表示首元素(一维数组char xxx[30])的地址,我们前面知道一维数组的步长为数组的长度*类型长度。故二维数组名字是第一行(一维数组)的地址; 首行地址和首元素的地址是一样的,但其步长不一样。
#include #include using namespace std; int main(int argv, char** argc) {char buff[3][30] = {"aaaaaa","bbbbb","cccccc"}; //相差90,&buff代表整个二维数组的地址,因此步长是整个二维数组 cout << "&buff:" << &buff << endl; cout << "&buff + 1:" << &buff + 1<< endl; //相差30,buff是第一个数组(二维数组看作是多个数组)的地址,因此步长是一个一维数组的大小,30 cout << "&buff:" << buff << endl; cout << "&buff + 1:" << buff + 1<< endl; system("pause"); }

再看一个例子,更好理解,有时数组名是数组名,有时是地址。
下面打印出来的是第二行首地址,因为a这里是地址,不是数组名,因为这里有加1,数组名是没有加1的。加1之后得到第二行的地址(也就是&buff),接着加* 得到buff(这里要将a+1看做一个第二行的地址,这里的第二行要看做一个整体,看做某种类型的变量,a+1不是int类型的指针,虽然跟第二个行收个元素(int类型)的地址一样)),我们在一维数组中知道,buff和&buff实际上值是一样的,一个代表一维数组首元素地址,一个代表一维数组的地址。
#include #include using namespace std; int main(int argv, char** argc) { int buff[4] = {0}; int a[3][4] = { 0 }; //buff cout << "*(a+1) = " << *(a + 1) << endl; //&buff cout << "(a+1) = " << (a + 1) << endl; system("pause"); }

那我们怎么得到第二行第二个元素地址和内容呢?因为上面+1都是一个一维数组一个一维数组地跳,a+1是第二个一维数组的地址,a[1](也就是 * (a+1))是第二个一维数组的名字也是首地址,所以a[1]+1也就是第二行第二个元素地址,a[1][1]或者* (a[1]+1)就是第二行第二个元素。
下面两种赋值哪种是正确的,第一种,首先p是一个大小为4的int数组的指针,而前面我们说过a刚好是第一行的地址(一维数组的地址),所以第一种,要令第二种正确,p应该怎么样定义呢?int (* p)[3][4]; 因为a是一个二维数组的地址。
还要注意p+1的跨度为4*4=16
#include #include using namespace std; int main(int argv, char** argc) { int a[3][4] = { 0 }; int (*p)[4]; p = a; p = &a; system("pause"); }

  • 二维数组求元素个数,行数,列数
#include #include using namespace std; int main(int argv, char** argc) { int a[3][4] = { 0 }; //二维数组元素个数 cout << "元素个数:sizeof(a)/sizeof(int) = " << sizeof(a) / sizeof(int) << endl; cout << "元素个数:sizeof(a)/sizeof(a[0][0]) = " << sizeof(a) / sizeof(int) << endl; //二维数组有多少行 cout << "行数:sizeof(a)/sizeof(a[0]) = " << sizeof(a) / sizeof(a[0]) << endl; //二维数组有多少列 cout << "列数:sizeof(a[0])/sizeof(int) = " << sizeof(a[0]) / sizeof(int) << endl; cout << "列数:sizeof(a[0])/sizeof(a[0][0]) = " << sizeof(a[0]) / sizeof(a[0][0]) << endl; system("pause"); }

  • 二维数组做形参
    数组做形参都会退化为指针,但步长不一样
#include #include using namespace std; //退化为指针,但步长仍然是4 void printArray(int a[3][4]) { for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { cout << a[i][j] << " "; } cout <

  • 指针长度
    无论什么类型指针,系统位数相同就是一样的
  • 结构体类型和对应变量定义
    如果不加上typedef就可以在结构体变量定义的时候不加struct。
  • void类型
    void a; 不行,因为不知道要给a分配多少个字节
    void *p; 可以,因为指针的字节在系统位数固定的时候也是固定的
    所以——数据类型的本质是固定内存块大小的别名
  • 最好的学习方法——重复
  • 固定写法

    C基础加强
    文章图片
  • 字符串赋值的问题
    如果是用数组的形式去获取字符串常量的值,那么会新建一个字符串数组,然后将字符串数组赋给变量str,而p跟q的值一样是因为字符串常量一样的时候其地址也是一样的。
#include #include using namespace std; void str() { char str[] = "123"; char *p = "123"; char *q = "123"; if (str == p) { cout << "str==p:true" << endl; } else { // 打印这个 cout << "str==p:false" << endl; }if (q == p) { //打印这个 cout << "q==p:true" << endl; } else { cout << "q==p:false" << endl; } }

  • 间接赋值是指针存在的最大意义
  • 需要用到二级指针的场合
    修改普通变量需要一级指针,修改一级指针(也是变量)需要二级指针
#include #include using namespace std; void changeSecondPtr(int *p) { p = (int *)0x2222; }void changeSecondPtr2(int **p) { *p = (int *)0x3333; }int main(int argv, char** argc) { int *p = (int *)0x1111; //只传一级指针改变不了指针的值 changeSecondPtr(p); cout << "p: " << p << endl; //改变得了指针的值 changeSecondPtr2(&p); cout << "p: " << p << endl; system("pause"); }

  • 指针在子函数中注意事项
    1.判断形参指针是否为NULL
    2.最好不要直接使用形参
#include #include using namespace std; int myStrcpy(char *src, char *dst) { if (src =https://www.it610.com/article/= NULL || dst == NULL) { return -1; }while (*dst++ = *src++); //这一行会出现问题,因为dst已经不是首地址了 cout <<"myStrcpy:" << dst << endl; return 0; }int myStrcpy2(char *src, char *dst) { if (src =https://www.it610.com/article/= NULL || dst == NULL) { return -1; }char *from = src; char *to = dst; while (*to++ = *from++); cout <<"myStrcpy2:" << dst << endl; return 0; }int main(int argv, char** argc) { char str1[] = "abc"; char str2[10]; char str3[10]; myStrcpy(str1, str2); myStrcpy2(str1, str3); system("pause"); }

  • const和指针
    不看类型,
    若直接修饰是 * ,那么指向的内容不能改变,指向可以改变。
    若直接修饰是p,那么指向的内容可以改变,指向不能改变。
    可以简单记为*代表其内容,p代表指针,const修饰到的就不能改变。
#include #include using namespace std; int main(int argv, char** argc) { char var; char buf[] = "abcd"; const char *p1 = buf; char const *p2 = buf; p1[0] = 'f'; //error! p1 = &var; //okchar * const p3 = buf; p3[0] = 'f'; //ok p3 = &var; //error!const char *const p4 = buf; p4 = &var; //error! p4[0] = 'f'; //error!system("pause"); }

  • C语言的const是个冒牌货
    可以通过指针间接修改变量的值,C++就避免了
int main(int argv, char** argc) { const int a = 5; int *p = &a; *p = 6; }

  • 二级指针做输入的三种模型
    第一种模型:之前变量要在子函数中改变,那么要传指针进去,现在字符串(字符指针)变量要改变,那么就要传二级指针进去。
    eg:char *a[] = {"1111","2222","3333"}(a是指针数组,见下面)要进行排序,那么函数在写的时候应该是这样sort(char **p)
    第二种模型:跟第一种差不多,只不过是char aa[3][30]={"1111","2222","3333"}; ,排序函数要这样写,sort(char buff[][30]),不能写乘sort(char **p),因为会退化为指针,排序的时候我们还是要30个30个就像一维数组那样子来。
    第三种模型:第一种分配在全局区域,第二种分配在内存区域,也就是栈,第三种是堆
  • 指针数组
    //[]的优先级要比高,我们首先看[]知道是个数组,接着char只不过是一个类型
    char *strs1[] = {"111","222","333"};
    记住下面,其实就是数组的逻辑
    sizeof(strs1) == 12
    sizeof(strs1[0]) == 4
    跟char **strs2的区别,strs2是一个指针,不能赋值{"111","222","333"},而strs1是一个数组,但strs2可以赋值一个指针数组元素的指针(指针的指针),通常是第一个
  • 数组指针
    数组指针的步长是整个数组大小,具体解释和格式看下面,记住两者的区别。
//数组指针类型,对比int a[10]; 中a是一个数组,相当于说是int [10]给中间的东西 //套上了一个数组类型,int [10]就类似于int等基本类型,现在int (*P)[10]就相当 //于int *p,所以下面语句中,P是一个指针类型,一个指向数组的指针类型。 typedef int (*P)[10]; //指针数组类型 int a, b; typedef int *T[10]; T buff = { &a,&b };

  • 结构体偏移地址trick
#include #include using namespace std; typedef struct Student { char name[50]; int age; } Student; int main(int argv, char** argc) { int pianyiliang = (int)&(((Student *)(0))->age); cout << pianyiliang << endl; system("pause"); }

  • 结构体内存对齐
    空间换取时间,如果没有对齐,需要读取多次,然后拼接,对齐只需要读取一次
    ...
  • 函数指针定义
    应用:函数调用,回调函数
#include #include using namespace std; void justPrint(int input) { printf("=============%d\n", input); }int main(int argv, char** argc) {typedef void FUN(int input); FUN *p1 = justPrint; p1(1); typedef void (*PFUN)(int input); PFUN p2 = justPrint; p2(2); void (*p3)(int a) = justPrint; p3(3); system("pause"); }

  • 函数指针数组
    fun先和[]结合得到数组,其他都是函数指针类型的写法。
#include #include using namespace std; void justPrint(int input) { printf("=============%d\n", input); }int main(int argv, char** argc) {void (*fun[5])(int input); fun[0] = justPrint; system("pause"); }

  • .c->.exe
    预处理:
    编译:语法检查,编译成汇编文件
    汇编:汇编文件变目标文件——二进制文件,.o
    链接: 结合多个.o文件编程.exe文件
  • include
    “”会优先查找本地,再查找系统
    <>只会查找系统
  • 【C基础加强】define

    推荐阅读