C++学习笔记_07 const、指针、引用 2021-04-19

简介: C++学习笔记_07 const、指针、引用 2021-04-19
//C++学习笔记_07 const、指针、引用
/*
我们写个程序:这个程序需要使用配置文件 conf.ini
这个程序的很多功能,都需要读取这个问题
每个要读配置的地方,写代码  FILE* pFile = fopen("conf.ini", "r");
如果要修改配置文件名 修改为 config.ini
-->我们需要在每个用到 conf.ini 的地方都进行修改  ==》改动很多
所以我们可以定义宏变量
#define CONF_FILE  "conf.ini"
FILE* pFile = fopen(CONF_FILE, "r");
--》我们要改文件名,只需要改宏定义  --- 可维护性更好
特别是碰到一些数字  -- MAX_MAC_LEN   ---》 自注释型的变量  --- 程序可读性更好
*/
/*
指针是什么东西?
    它就是一个地址(任何变量类型的指针),占 4 个字节
    变量类型, 只是用于标志  这个地址里面存储的变量  是什么样的变量类型。
多级指针是什么东西?
int **p;
我们一般只是说 p 是一个二级指针
其实,二级指针本质上 就是一个普通指针
只是说,这个指针 存储地址  ---》这个地址对应的内存 存储的数据 还是一个地址
       (这里,p 的值,就是 一个 int* 变量的地址)
指向对象的指针?
AAA *pA = new AAA()
new AAA() :   申请 sizeof(AAA) 这个大小的内存,存储一个 AAA() 对象
AAA *pA = new AAA("C++")
new AAA("C++") : 申请 sizeof(AAA) 这个大小的内存,存储一个 AAA("C++") 对象 (AAA A("C++"))
这里生成了一个新的对象 ---》调用对应的构造函数来生成对象
对象构造成功后,会把这个对象的地址返回出来,使用 pA 进行保存
--》pA 保存的就是一个4字节地址 (标志对象存储在哪个地方)
使用 new 生成的对象,使用完后,需要我们主动调用  delete 释放对象占用的内存
释放的是的对象本身占用的内存,如果对象内部申请了内存 --》在对象被删除的时候,
会调用析构函数 (在析构函数里面释放对象自己申请的内存)
如果 new 生成的对象,没有被 delete ---》对象不会被销毁 --》从而不会调用析构函数
什么是函数指针?
也是一个指针, 保存了一个地址,只不过 这个地址上,存储的是一个函数
定义函数指针: 
   void (*pFun)(int a, int b):    关键点 (*pFun)
   void           表示 pFun 指向的函数, 返回值是 void
   (int a, int b) 表示 pFun 指向的函数,入参是 (int,int), 这里定义的时候,a 和 b 是可以省略的
我们可以定义一个函数指针类型 (别名)
typedef void (*pFun)(int a, int b)   //在上面的基础上,前面加一个 typedef
==》pFun 的意义完全不一样了!!!!!
==》pFun 代表的是一个变量类型 (不再是变量名),我们可以通过这个变量类型定义变量
==》pFun  fun;  fun 表示 pFun 这个变量类型的变量
fun 的变量类型就是 void(*)(int, int) 
关于内存使用:
1:申请一片内存  N 个字节
  malloc(N)                  
  ---> 赋值的时候,一般要强转: 
  char *pStr = (char*)malloc(4);  int *pInt = (int*)malloc(4);
2:内存清零
  memset(p,0, N)   把 p 这个地址往后 N 个字节的内存清零
3:内存拷贝
  memcpy(pDst, pSrc, N)  把 pSrc 这个内存位置的值,赋值到 pDst 这个内存位置 
  (长度是 N 个字节)
memset(0xabcd, 0 , 1000000000);  //memset 把某一片内存地址,按字节,置位为某个值
*/
#include<iostream>
#include<cstring>
using namespace std;
//编译的步骤:编译预处理 --》 编译 --》 汇编 --》链接
//const 修饰变量,变量值不允许修改
//C++ 派:都已 const 来定义宏变量,替代C语言内的 define
const int M = 8; //优点 1 : 有数据类型,代码更加严谨
                 //优点 2 : 使用 #define 编译前做替换,每一个数字都是一个独立的变量 (都需要占用内存)
                 //         const 变量,只有一份副本           
#define N 8     //我们在程序里面使用 N 的地方,在编译前(编译预处理),用 8 来替换
#define MAX(a, b) (a > b ? a:b)   //注意,这种写法不标准
#define DIV(a, b) ((a) / (b))
void TestDefine()
{
    //#define 是在编译前,先做替换
    cout << N << endl;  //==> cout << 8 << endl;
    cout << MAX(10, 11) << endl; // cout << (10>11?10:11) << endl;
    cout << DIV(20, 4) << endl;    // ==> 20/4
    cout << DIV(16 + 4, 4) << endl; //==> 16+4/4  / (16+4)/4
    cout << 20 / DIV(10, 5) << endl; // 20/(10)/(5)  ==> 0 //需要在外部加上()
}
//申请一片内存并返回
int* const GetMem1(int len)
{
    int *p = (int*)malloc(len*sizeof(int));
    if (p != NULL)
        memset(p, 0, len*sizeof(int));
    return p;
}
const int* GetMem2(int len)
{
    int *p = (int*)malloc(len*sizeof(int));
    if (p != NULL)
        memset(p, 0, len*sizeof(int));
    return p;
}
class AAA
{
private:
    int x;
public:
    AAA() :x(0){}  //构造函数;:x(0) 表示给x赋值 (x=0)
    void Print() const { cout<<"x = "<<x<<endl;} //x++;函数内部,不允许修改 类的数据成员   
            //类的成员函数后面,加上const, 表示这是一个常成员函数
                        //1: 函数内部,不允许修改 类的数据成员
                        //2: 只能调用 const 修饰的成员函数
    //PrintNext();  //不能调用非 const 修饰的成员函数,哪怕 PrintNext 没有修改数据
    //如果要调用,必须 把 PrintNext 定义成常成员函数
    void PrintNext() {cout << "next:" << x + 1 << endl;}//const
    //一般情况下,我们定义一个成员函数,如果不涉及修改成员数据,我们都声明成常成员函数
};
void PrintUpper(const char *pStr) //大写输出 pStr //函数内部不允许修改 *pStr       
{
    char c;
    while (*pStr){
        c = *pStr;
        //注意,这里 (*pStr) 必须加()
        //我们不需要大家记运算符优先级:一律以 () 来强制指定优先级
        //if (*pStr >= 'a' && *pStr <= 'z')  (*pStr) += ('A' - 'a');
        if (c>='a' && c<='z')  c+= ('A'-'a');
        cout << c;
        pStr++;
    }
    cout << endl;
}
void TestConst()
{
    //const 用于修饰变量或者函数
    // 修饰变量:表示这个变量不允许被修改 (称之为常量)
    // 修饰普通函数:表示这个函数的返回值 是 const 类型
    // 修饰成员函数:表示这个成员函数不能修改成员变量 (称之为常成员函数)
    const int x = 9;  //必须赋初值。(这里不赋值,没机会赋值了)
    int const y = 11; //两种写法,意义一样
    cout << "x:" << x << endl;
    //x = 10; x 的值不允许修改
    int a = 10;
    int b = 20;
    const int *p = &a; // p 变量的值是  a 的地址  
                       // *p 的值不能改 (可以通过 a=11修改值,但是不能通过 *p=11修改a的值)
    int const *q; //这个写法和上面等价
    int* const r = &a; //这里和上面不一样:可以通过 *r 来修改 a的值, 但是 r 不能重新赋值    
    //总之,我们看const的位置,const 后面是 *p 表示 *p 不能改,const 后面是 p, 表示 p 不能改
    //*p = 11; 这个不允许
    p = &b;    //这个是允许的
    *r = 11; //这个是允许的
    //r = &b;  //这个是不允许的
    int const* const p1 = &a; //第二个const 修饰 p 表示 p不能改
                              //第一个const 修饰 *p 表示 *p 不能改  
    const int* const p2 = &b; //和上面写法等价
    int *pInt = GetMem1(10); //GetMem2 返回 int* const 的变量
                           //const 修饰函数返回值,这个函数 不能直接作为左值使用。
    cout << *pInt << endl;
    pInt[0] = 11; //等价于 *(pInt + 0)
    cout << *pInt << endl;
    const int *pArr = GetMem2(10); //GetMem2 返回 const int* 的变量, 只能赋值给 const int * 的变量
    //const int*  或者 int* const 修饰函数,其实就是修饰的函数返回值
    //在定义函数的时候, 如果,我们不允许函数内部修改入参的值,我们也可以用 const 来修饰入参
    //一般 搭配 引用来使用
    char  pStr[] = "hello world!";
    //char *pStr = "hello world!"; //这个 hello world  不允许修改
    //                             PrintUpper 里面,事实上修改了它的值
    //pStr++;                  //这个地方那个 pStr++
    //char *p = pStr; p++;     //这样是允许的
    PrintUpper(pStr); //PrintUpper 里面的 pStr 是他的局部变量,可以++
    cout << "pStr:" << pStr << endl;
}
void Swap(int a, int b) //交换 变量 a 和 b 的值
{ int tmp = a; a = b;b = tmp;return; }
void Swap2(int &a, int &b) //入参是引用
{ int tmp = a;a = b;b = tmp;return; }
void Swap3(int *pa, int *pb)  //C语言中,一般使用指针的方式
{ int tmp = *pa; *pa = *pb; *pb = tmp; return; } 
//入参的变量类型是  int*  ---> 表示我们调用时候,应该传入一个地址
class BBB
{
private:
    int x;
    char *pInfo;
public:
    BBB() :x(0), pInfo(new char[10])
    { cout << "默认构造 " << endl;};
    BBB(int a) :x(a),pInfo(new char[10])
    { cout << "构造函数" << endl; }
    ~BBB()
    { cout << "析构函数" << endl;delete[] pInfo; }
    void IncSelf(){ x++; }
    void Print() const{ cout << x << endl; }
};
void PrintObj(const BBB &B)
{
    //B.IncSelf(); // IncSelf 没有被const 限定,那么,它可能会修改 B 
                   //但是,我们这个入参限定了 B 是不能改的
                   //所以,不能调用 IncSelf() 这个函数
    B.Print(); //Print 是常成员函数,不会改变对象,所以,这里可以调用
    //不使用引用的情况,入参 BBB B 初始化的时候,会生成一个新的对象,是IncAndPrint的局部变量
    //return之前会调用它的析构函数  --》释放内存
    //使用引用的情况,入参 BBB &B 初始化的时候,不会生成新的对象
    //只是生成了一个对象的引用:有一个隐含指针*p,指向了调用函数的那个实参
    //                          我们使用 B 的时候,系统实际上使用的是 *p
    //任何时候,定义变量,变量名前面加 *, 表示这里定义的是一个指针,保存的值的地址
    //          *前面的变量类型,只是说明,这个地址上,存储的是一个什么类型的变量
    BBB *pB = new BBB(20); //这里 pB 不是对象,对象存在堆内存中,pB 的值就是这个地址
    //B = *pB;
    delete pB; //释放 pB 申请的内存 (也就是保存 BBB 对象的堆内存)
               //delete 的时候,也会调用 BBB 对象的析构函数,释放资源
}
void TestRef()
{
    //引用: 定义变量的时候,在变量名前面加上 &。 表示这个变量是引用
    //      1:必须赋初值
    //      2:这个初值,必须是一个同类型的变量
    int x = 10;
    int &rx = x;  //这个就是定义一个引用 rx, 使用 rx 的时候,就相当于使用 x
            //只有在定义变量的时候,变量名前面加上 & ,则表示 定义 引用
    cout << "rx = " << rx << endl;
    x = 11;
    cout << "rx = " << rx << endl;
    rx = 20;
    cout << "x = " << x << endl;
    //引用有什么作用 ?
    int xx = 11;
    int yy = 22;
    Swap(xx, yy); //为什么不能改变 xx,和 yy 的值?
        //Swap(int a, int b) 这里 a 和 b 是 Swap的局部变量
        //在函数调用 Swap(xx, yy) 的时候,相当于先执行  int a = xx, int b = yy
        //然后,在执行 Swap 函数内部的代码, 这里面操作的是 a 和 b 跟 xx,yy 没关系啦
    cout << "(x, y) = (" << xx << ", " << yy << ")" << endl;
    Swap2(xx, yy); //不要理解为取地址!
        //还是一样的,Swap2(int &a, int &b) 中,a 和 b 是 Swap2 的局部变量
        //函数调用 Swap2(xx, yy) 的时候,相当于先执行  int &a = xx, int &b = yy
        //a 和 b 是 xx 和 yy 的引用,操作 a 和 b 就相当于操作  xx 和 yy
    cout << "(x, y) = (" << xx << ", " << yy << ")" << endl;
    //这里,&xx, &yy 表示传入 xx 和 yy 的地址。 
    //函数调用时候赋值: int* pa = &xx; int* pb=&yy;
    Swap3(&xx, &yy);
    cout << "(x, y) = (" << xx << ", " << yy << ")" << endl;
    //引用 和 const 配合
    BBB  B1(10);
    //函数调用的时候:IncAndPrint(BBB B) 初始化局部变量 B
    //                       B = B1
    //这个赋值比较简单。如果 B 是一个很复杂的类,数据成员很多
    //1:这个赋值,会把成员依次赋值。需要占用 sizeof(BBB) 大小的空间,复制操作比较多
    //2:如果涉及到申请和释放资源,入参 B 会对资源进行释放 (如果没做处理,容易导致多次释放资源)
    //所以,一般情况下,我们入参定义成引用
    PrintObj(B1);
    B1.Print();
    //新的问题:我们给函数传入引用,那么,函数内部就可以直接修改我们的对象了
    //所以,使用引用作为函数入参,如果我们不涉及到修改入参变量,一般,我们使用 const 进行约束
    //      --》不允许函数内部修改 对象的值
    //定义一个指针的引用 (使用这个引用的时候,相当于使用某个指针)
    char* p = "Hello world"; //变量类型是 int*
    //在对空间中划一片内存存放 "Hello world", 然后把这个地址赋值给 p
    char* &rp = p;           //变量类型是 int*  变量名是 &rp, 变量名前面加& 表示这个变量是引用
}
int sum(int a, int b){ return a + b;}
typedef int(*FUN)(int, int);
void TestFunPtr()
{
    int(*pFun)(int, int); //这一行的意思:就是定义一个指针 pFun, 这个pFun存储的是一个函数的地址
                          //--> pFun 就是一个函数指针
    //pFun 指向的是一个入参是 (int, int),返回值是 int 的函数
    //              任何这个类型的函数,都可以赋值给 pFun
    pFun = sum;  //注意这里 sum 没有括号
    //关于函数,从这里我们可以看出来,使用的时候,可以当做变量一样使用
    //其实,函数,本质上就是一个变量,而且,是全局变量
    //      变量类型:int(*)(int, int)   --》这是一个指针(函数指针)
    //      变量值  :就是函数体
    //-->任何一个变量,都是用于标志一片内存
    int cnt = pFun(10, 21); //相当于调用 sum(10,21)
    cout << cnt << endl;
    cnt = (******pFun)(11, 22);  //对于函数指针来说,*pFun 和 pFun 无区别(无论加多少*都是一样的)
    cout << cnt << endl;
    //--> 一般 函数指针使用的时候,不需要加 *
    //int *p = (int*)malloc(4);
    //*p = (int)p;
    //p == *p == **p == ***p
    FUN fun;  //这个fun就是变量类型了 ==》这个时候,我们函数的入参,就可以是函数了
    fun = sum;
}
int FunFun(int a, int b, FUN  fun){
    return fun(a, b);  //这个就是回调函数的雏形
}
int main()
{
    //TestDefine();
    //TestConst();
    //TestRef();
    TestFunPtr();
  return 0;
}
//作业:自己实现 MyString 类
//要求 使用方式尽量 和 string 的使用方式一样 (增删改查)
相关文章
|
12小时前
|
C++ 数据格式
LabVIEW传递接收C/C++DLL指针
LabVIEW传递接收C/C++DLL指针
12 1
|
12小时前
|
编译器 C++
C/C++杂谈——指针常量、常量指针
C/C++杂谈——指针常量、常量指针
8 0
|
12小时前
|
C++
c++引用是什么意思?
c++引用是什么意思?
6 2
|
12小时前
|
C++
c++引用看这个就够了
c++引用看这个就够了
9 0
|
12小时前
|
C++ 编译器
|
12小时前
|
存储 安全 程序员
C++:智能指针
C++:智能指针
20 5
|
12小时前
|
存储 安全 C++
深入理解C++中的指针与引用
深入理解C++中的指针与引用
10 0
|
12小时前
|
C语言
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
|
12小时前
|
存储 程序员 编译器
爱上C语言:指针很难?来来来,看看这篇(基础篇)
爱上C语言:指针很难?来来来,看看这篇(基础篇)
|
12小时前
|
C语言
c语言指针总结
c语言指针总结
15 1