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 <<  endl;
    }
}

//退化为指针,但步长仍然是4
void printArray2(int a[][4]) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
}

//这种错误,a+1只跳4个字节,应该要16个字节
void printArray3(int **a) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
}

//数组指针
void printArray4(int (*p)[4]) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cout << p[i][j] << " ";
        }
        cout << endl;
    }
}

//数组指针定义为类型
typedef int(*P)[4];
void printArray5(P p) {
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cout << p[i][j] << " ";
        }
        cout << endl;
    }
}

int main(int argv, char** argc) {
    int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
    printArray5(a);


    system("pause");
}
  • 指针长度
    无论什么类型指针,系统位数相同就是一样的

  • 结构体类型和对应变量定义
    如果不加上typedef就可以在结构体变量定义的时候不加struct。

  • void类型
    void a;不行,因为不知道要给a分配多少个字节
    void *p;可以,因为指针的字节在系统位数固定的时候也是固定的
    所以——数据类型的本质是固定内存块大小的别名

  • 最好的学习方法——重复

  • 固定写法


    C基础加强_第1张图片
  • 字符串赋值的问题
    如果是用数组的形式去获取字符串常量的值,那么会新建一个字符串数组,然后将字符串数组赋给变量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 == NULL || dst == NULL) {
        return -1;
    }

    while (*dst++ = *src++);
    //这一行会出现问题,因为dst已经不是首地址了
    cout << "myStrcpy:" << dst << endl;
    return 0;
}


int myStrcpy2(char *src, char *dst) {
    if (src == 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;//ok

    char * 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
    “”会优先查找本地,再查找系统
    <>只会查找系统

  • define

你可能感兴趣的:(C基础加强)