9.1 变量的声明与定义
变量声明:用于向程序表明变量的类型和名称。通过使用关键字 extern 声明,即不分配存储空间。编译器识别 extern 即知,变量在他处定义。注意:只有当 extern 声明位于函数外部时,才可以被初始化(可初始化说明已分配内存地址,等于变量定义)。
变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。当变量定义时,也同时向程序表明了变量的类型和名称,即变量定义可以理解为变量声明+分配内存地址。定义提供了一个变量在程序中的唯一描述,所以在一个程序中,变量只能定义一次(即只分配一次内存地址),但声明可以多次。
#include <iostream> using namespace std; extern float pi = 3.14; //可以被初始化 int main() { cout << "pi=" << pi << endl; //输出:pi=3.14 cout << "&pi=" << &pi << endl; //输出:&pi=00007FF77B03E00C,说明已分配地址 extern int a1; //extern声明但不定义 extern int a1; //多次声明,不会报错 extern int a2=1; //错误C2205:不能对带有块范围的外部变量进行初始化。不能被初始化 cout << "&a1=" << &a1 << endl; //链接器工具错误LNK2001:链接器无法解析对变量的引用 int b; int b; //错误C2086:重定义 cout << "b=" << b << endl; //错误C4700:使用了未初始化的局部变量“b” cout << "&b=" << &b << endl; //输出:&b=000000EF8977F9F4;说明:变量被定义,已分配内存空间 }
9.2 函数的声明与定义
函数声明:是向编译器表明函数名称、参数数量及类型、函数返回类型。声明不会分配内存地址。
函数定义:是为函数分配内存地址,且包含实现函数功能的函数体{ …… }。同样,函数定义只能一次,但可以声明多次。
#include <iostream> using namespace std; int add1(int x, int y); //函数声明 int add2(int x, int y) //函数定义 { return x + y; }; int main() { cout << "add1()地址:" << &add1 << endl; //错误 LNK2019:调用未定义的函数(编译器认为函数存在, 但链接器不能解析对地址的调用) cout << "add2()地址:" << &add2 << endl; //输出:add2()地址:00007FF76ACA1460 }
9.3 引用与指针
指针:是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元(a pointer which point to the address of a variable)。通过地址操纵对象,可以有效地表示复杂的数据结构,用于数组和函数的控制。指针定义的一般格式如下:
数据类型 *变量名1,*变量名2,……,*变量名n;
注意:“ *”是一个标志,表示所定义的变量是一个指针变量,以示与一般变量的区别,定义时星号的位置居左、居中、居右都是正确的(Visual Studio 2022中默认位置居左)。指针变量的类型与其所指向的数据的类型必须相同。
int* p2 = &a1; //指针名是p2,不是* p2, 此处的 * 可理解为定义的标识 cout << "&a1=" << &a1 << endl; //输出:&a1=000000B27CAFFA64 cout << "p2=" << p2 << endl; //输出:p2=000000B27CAFFA64,是a1变量的地址 cout << "*p2=" << *p2 << endl; //输出:*p2=2(a1的值),* 代表解引用,找到指针指向的变量
引用:是变量的一个别名,实质上是同一个地址上存储的变量,对引用的操作与对变量直接操作完全一样。注意:有数据类型(如int& a=b)时,&表示引用,否则&是取地址运算符,如&a表示取a变量的地址。引用时C++对C的一个重要扩展。
int& b2 = a1; cout << "&a1=" << &a1 << endl; //输出:&a1=000000B27CAFFA64 cout << "&b2=" << &b2 << endl; //输出:&b2=000000B27CAFFA64,与a1变量的地址相同
引用和指针的特性:
① 引用被创建时,必须初始化,而指针可以是空值,可在任何时候被初始化。注意:如果指针没有初始化,指向一个未知的地址,在读取数据时,可能会造成程序崩溃。
int& b1; //错误C2530:“b1” : 必须初始化引用 int& b2 = a; //ok int* p1; //ok
②引用只能是一级,指针可以有多级。
int&& e1=a; //错误C2440:“初始化”: 无法从“int”转换为“int && ” int** e2; //ok,此处**也是起到标识作用
③引用在初始化后不可改变,指针在初始化后可以改变(即指向其它的存储单元)。
int& b2 = a1; //引用 int& b2 = a2; //错误C2374:b2重定义,多次初始化 b2 = a2; //相当于把a2赋值给a1,b2的地址还是与a1的地址相同 int* p2 = &a1; //p2为a1变量的地址 p2 = &a2; //p2变为a2变量的地址
④可以用常量或表达式对引用进行初始化,此时须用const作声明。对表达式初始化时,编译系统生成一个临时变量(在内部实现的,用户不能访问),用来存放该表达式的值,引用是该临时变量的别名。通过const声明,不仅可以用表达式对引用进行初始化,还可以用不同类型的变量进行初始化。
int i = 4; const int& f = i + 1; cout << "i=" << i << endl; //输出:i=4 cout << "f=" << f << endl; //输出:f=5 cout << "&i=" << &i << endl; //输出:&i=00000009146FF5D4 cout << "&f=" << &f << endl; //输出:&f=00000009146FF614,与i的地址不同
此外,通过const声明指针时,要注意常量指针、指向常量的指针以及指向常量的常量指针的差别。
int* const pA; //常量指针,不能再指向其他地址,但可以修改pA地址指向的值 const int *pB; //指向常量的指针 , 不能修改指向的值,但可以修改pB的指向 const int* const pC; //指向常量的常量指针
⑤ 一个指针占据的内存空间大小与其指向的数据类型无关,与计算机类型所能寻址的位数有关。
cout << "int型指针内存占位=" << sizeof(int*) << endl; //int型指针内存占位=8,计算机是64位的 cout << "char型指针内存占位=" << sizeof(char*) << endl; //char型指针内存占位=8 cout << "double型指针内存占位=" << sizeof(double*) << endl; //double型指针内存占位=8 cout << "float型指针内存占位=" << sizeof(float*) << endl; //float型指针内存占位=8
⑥指针重要作用之一是在运行阶段分配未命名的内存以存储数据。C++可使用new运算符来分配内存,运行阶段需要为哪种数据类型分配内存,new找到一个长度正确的内存空间,并返回该内存空间的地址。将该地址赋给一个指针,即可实现对该地址内的值进行操作。该内存地址的数据使用完成后使用delete运算符释放内存地址,释放的内存可供程序的其他部分使用。注意:不要使用delete来释放不是new分配的内存;不要使用delete释放同一个内存块两次;如果使用new为数组分配内存,则应使用delete来释放。
int* p6 = new int; *p6 = 10; cout << "sizeof *p6=" << sizeof(*p6) << endl; //输出:sizeof *p6=4 cout << "*p6=" << *p6 << endl; //输出:*p6=10 cout << "p6=" << p6 << endl; //输出:p6=000002119D016B60 delete p6; //cout << "*p6=" << *p6 << endl; //测试报错:读取访问权限冲突,p6 是 0x8123 cout << "p6=" << p6 << endl; //输出:p6=0000000000008123 int *p7=new int; //指针p7为数组第一个元素的地址,*p7为第一个元素的值 ... delete p7; //释放内存
⑦数组名称是数组第一个元素的指针。使用指针变量指向数组的首地址,即可访问该数组。
int abc = { 2,3,4,5 }; cout << "abc地址=" << &abc << endl; //输出:abc地址=000000717D36F908,与abc一致 cout << "abc地址=" << &abc << endl; //输出:abc地址=000000717D36F908 cout << "abc地址=" << &abc << endl; //输出:abc地址=000000717D36F90C cout << "abc地址=" << &abc << endl; //输出:abc地址=000000717D36F910 cout << "abc地址=" << &abc << endl; //输出:abc地址=000000717D36F918 int* p3 = abc; for (int i = 0; i < 4; i++) { cout << *(p3++) << " "; //使用指针遍历数组 }
⑧ 指针可以进行加法+/减法-运算,以及自增++/自减–运算,指针的运算就是地址的运算,指向的对象也相应的改变。此外,两个指针相减能获得两指针地址之间的距离,且分正/负;两指针相加没有意义。注意:C++不会对访问的地址进行检查,通过指针运算,指针访问一块未分配/分配给其他对象的内存,但是没有任何报错,会造成取值的错误。
int nums4 = { 11,22,33,44,55,66,77 }; int* p4 = nums4; int* p5 = nums4 + 3; cout << "*(nums4 + 2)=" << *(nums4 + 2) << endl; //输出:*(nums4 + 2)=33 cout << "*(p4 + 2)=" << *(p4 + 2)<< endl; //输出:*(p4 + 2)=33 cout << "p4 =" << p4 << endl; //输出:p4 =00000027D334F618 cout << "(p4 + 1)=" << p4 + 1 << endl; //输出:(p4 + 1)=00000027D334F61C;加了4个字节 cout << "(p4 + 2)=" << p4 + 2 << endl; //输出:(p4 + 2)=00000027D334F620;了8个字节 cout << "(++p4)= " << ++p4 << endl; //输出:(++p4)= 00000027D334F61C,与p4+1地址一致 cout << "(--p4)= " << --p4 << endl; //输出:(--p4)= 00000027D334F618 cout << "p5 - p4 =" << p5 - p4 << endl; //输出:p5 - p4 =3 cout << "p4 - p5 =" << p4 - p5 << endl; //输出:p4 - p5 =-3 cout << "(p4 +10)=" << p4 + 10 << endl; //输出:(p4 +10)=000000F73A2FF730 cout << "*(p4 +10)=" << *(p4 + 10) << endl; //输出:*(p4 +10)=-858993460
⑨ 表示不确定的类型的用void型指针,void型指针是一种通用型指针,任何类型的指针值都可以赋给void类型的指针变量。但对已赋值的void型指针,如再对其输出或者传递指针值等操作时,则必须显式类型转换,否则会出错。
void* p7; int m = 99; char n = 'a'; p7 = &m; cout << "p7=" << p7 << endl; //输出:p7=000000292B8FFAB4 //cout << *p7 << endl; //错误E0852:表达式必须是指向完整对象类型的指针 cout << "*(int*)p7=" << *(int*)p7 << endl; //输出:*(int*)p7=99 p7 = &n; cout << "p7=" << p7 << endl; //输出:p7=000000292B8FFAD4 cout << "*(char*)p7= " << *(char*)p7 << endl; //输出:*(char*)p7= a
⑩ 指针的值(即地址)的四种状态:指向一个对象的地址;指向紧邻对象所占空间的下一个位置;没有指向任何对象(空指针,指针变量赋值为nullptr,在C++中,NULL表示0,程序数据不会存储在地址为0的内存中);无效指针(未经初始化的指针就是个无效指针,为避免出现无效指针,在不知指针指向时,使用nullptr进行赋值)。
测试程序如下:
//自学 C++ 第 9 课 声明与定义、引用与指针 #include <iostream> using namespace std; extern float pi = 3.14; int add1(int x, int y); int add2(int x, int y) { return x + y; }; void swap1(int xx, int yy) { int temp = xx; xx = yy; yy = temp; } void swap2(int* xx, int* yy) { int temp = *xx; *xx = *yy; *yy = temp; } int main() { cout << "pi=" << pi << endl; cout << "&pi=" << &pi << endl; int a1; int a2 = 2; //cout << "a=" << a << endl; //错误提示:使用了未初始化的局部变量“a”; cout << "&a1=" << &a1 << endl; //输出结果:&a=000000EF8977F9F4;说明:变量声明后,内存空间已分配; a1 = 1; //int a; //int& b1; //错误C2530:“b1” : 必须初始化引用 int& b2 = a1; //"引用"就是变量的别名,对"引用"操作与对变量直接操作完全一样。 b2 = a2; int* p1; p1 = &a1; p1 = &a2; int* p2 = &a1; //p2 = &a2; cout <<"a1=" << a1 << endl; cout << "a2=" << a2<< endl; cout << "&a1=" << &a1 << endl; cout << "&a2=" << &a2 << endl; cout << "*(&a1)=" << *(&a1) << endl; cout << "b2=" << b2 << endl; cout << "&b2=" << &b2 << endl; cout << "*(&b2)=" << *(&b2) << endl; cout << "p1=" << p1 << endl; cout << "p2=" << p2 << endl; cout << "*p1=" << *p1 << endl; cout << "*p2=" << *p2 << endl; cout << "&(*p2)=" << &(*p2) << endl; const char* c = "hello"; //hello在内存是怎么存储的?占几个字节? cout << "c=" << c << endl; cout << "&c=" << &c << endl; //是首地址还是整个hello单词的地址? extern int d1; //extern声明但不定义。 //extern int d2=1; //错误C2205:不能对带有块范围的外部变量进行初始化 //cout << "&d=" << &d << endl; //链接器工具错误LNK2001:链接器无法解析对变量的引用。 extern int d1; int x = 1, y = 2; cout << "x+y=" << add2(x,y) << endl; //cout << "add1()地址:" << &add1 << endl; 错误 LNK2019:调用未定义的函数(编译器认为函数存在, 但链接器不能解析对地址的调用) cout << "add2()地址:" << &add2<< endl; //int&& e1 = a;//错误C2440:“初始化”,无法从“int”转换为“int && ” int** e2; int i = 4; const int& f = i + 1; cout << "i=" << i << endl; cout << "f=" << f << endl; cout << "&i=" << &i << endl; cout << "&f=" << &f << endl; cout << "int型指针内存占位=" << sizeof(int*) << endl; cout << "char型指针内存占位=" << sizeof(char*) << endl; cout << "double型指针内存占位=" << sizeof(double*) << endl; cout << "float型指针内存占位=" << sizeof(float*) << endl; int abc = { 2,3,4,5 }; cout << "abc地址=" << &abc << endl; cout << "abc地址=" << &abc << endl; cout << "abc地址=" << &abc << endl; cout << "abc地址=" << &abc << endl; cout << "abc地址=" << &abc << endl; int* p3 = abc; for (int i = 0; i < 4; i++) { cout << *(p3++) << " "; } cout << endl; int nums4 = { 11,22,33,44,55,66,77 }; int* p4 = nums4; int* p5 = nums4 + 3; cout << "*(nums4 + 2)=" << *(nums4 + 2) << endl; cout << "*(p4 + 2)=" << *(p4 + 2)<< endl; cout << "p4 =" << p4 << endl; cout << "(p4 + 1)=" << p4 + 1 << endl; cout << "(p4 + 2)=" << p4 + 2 << endl; //cout << "(p4++)= " << p4++ << endl; cout << "(++p4)= " << ++p4 << endl; //cout << "(p4++)= " << p4++ << endl; cout << "(--p4)= " << --p4 << endl; cout << "p5 - p4 =" << p5 - p4 << endl; cout << "p4 - p5 =" << p4 - p5 << endl; cout << "(p4 +10)=" << p4 + 10 << endl; cout << "*(p4 +10)=" << *(p4 + 10) << endl; int xx = 8; int yy = 80; swap1(xx, yy); //运行结果并没有交换,值传递是把参数进行拷贝,把xx拷贝,yy拷贝,交换的是拷贝,原值没有影响;形参的改变无法引起实参的改变,交换的仅仅是形参。 cout << "xx = " << xx << " yy = " << yy << endl; swap2(&xx, &yy); //运行结果进行了交换,实参传递的是地址,形参是指针变量,通过指针的方式来交换实参 cout << "xx = " << xx << " yy = " << yy<< endl; int* p6 = new int; *p6 = 10; cout << "sizeof *p6=" << sizeof(*p6) << endl; cout << "*p6=" << *p6 << endl; cout << "p6=" << p6 << endl; delete p6; //cout << "*p6=" << *p6 << endl; cout << "p6=" << p6 << endl; void* p7; int m = 99; char n = 'a'; p7 = &m; cout << "p7=" << p7 << endl; //cout << *p7 << endl; //报错 cout << "*(int*)p7=" << *(int*)p7 << endl; p7 = &n; cout << "p7=" << p7 << endl; cout << "*(char*)p7= " << *(char*)p7 << endl; return 0; }
每课三题:
① delete和delete的区别?
答:delete只会调用一次析构函数,而delete会调用每个成员的析构函数;用new分配的内存用delete释放,用new分配的内存用delete释放。
② 什么是野指针?
答:野指针是未初始化或未释放的指针,其指向的内存地址非程序员所需的,可能指向了受限的内存。
③ 链表和数组的区别?
答:一是存储形式,数组是一块连续的空间,链表是不连续的动态空间,每个结点要保存相邻结点的指针。二是数据查找方式,数组查找使用地址偏移,速度快,链表需要按顺序检索结点,效率低。三是数据的插入或删除,链表可以快速插入和删除结点,而数组则可能需要大量的数据移动。四是越界问题,链表不会越界,数组存在越界的可能。(第2~4点的不同皆因第1点存储形式不同造成的)
自学 C++ 第 1 课 数字大小排序
自学 C++ 第 2 课 数组包含关系
自学 C++ 第 3 课 正整数反序输出
自学 C++ 第 4 课 计算体积及表面积
自学 C++ 第 5 课 杨辉三角
自学 C++ 第 6 课 二维数组找最值
自学 C++ 第 7 课 跳跃游戏
自学 C++ 第 8 课 数据合并排序
本文【二维指针初始化_指针初始化和赋值的区别】由作者: Web服务 提供,本站不拥有所有权,只提供储存服务,如有侵权,联系删除!
本文链接:https://www.cuoshuo.com/blog/4130.html