//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 的使用方式一样 (增删改查)