C++学习笔记_07 const、指针、引用 2021-04-19
2023-09-27 14:25:47 时间
//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 的使用方式一样 (增删改查)
相关文章
- 清华大学C++课程学习笔记——第三章函数
- 23 DesignPatterns学习笔记:C++语言实现 --- 2.4 Composite
- c++笔记3
- C语言学习笔记 (003) - C/C++中的实参和形参(转)
- 《C++游戏开发》笔记十一 平滑动画:不再颤抖的小雪花
- Item 8:析构函数不要抛出异常 Effective C++笔记
- 《C++游戏开发》笔记十二 战争迷雾:初步实现
- C++ const引用
- C++学习笔记_21 优先级队列实现-堆积树-堆排序 2021-05-24
- C++学习笔记_17 线性容器-Deque容器 2021-05-18
- C++学习笔记_13 双向链表和链表模板 2021-05-06
- C++学习笔记_04抽象类、多态 2021-04-15
- 传智播客 C/C++学习笔记 数组和指针的关系, 字符串学习
- 传智播客 C/C++学习笔记 多级指针
- 【C++】侯捷C++面向对象高级编程-笔记
- 学习C++笔记136
- C++学习笔记
- VC++/MFC学习笔记(一)
- 《C++ Primer 第5版》-14.3~14.7算术和关系、赋值、下标、递增和递减、成员访问运算符-康奈尔笔记
- 《C++ Primer 第5版》-13.1拷贝、赋值与销毁-康奈尔笔记
- C++ 常用设计模式(学习笔记)
- C++笔记:面向对象编程(Handle类)
- 设计模式C++学习笔记之七(AbstractFactory抽象工厂模式)
- 设计模式C++学习笔记之四(Multition多例模式)
- C++MFC编程笔记day05 文档类-单文档和多文档应用程序
- C++windows内核编程笔记day11 win32静态库和动态库的使用