你的c++学习路上明灯
今天我们开始学习c++的第一篇文章,讲的也是比较简单,不知道有没有人能坚持下来,陪我一起度过这个寒假,害,加油吧!
好了,我们开始今天的学习。
一,内存存储模型
首先我们要知道的是,在c++程序执行前后,系统会将内存大方向划分为4个区域
c++在程序运行前分为全局区和代码区
程序运行后分为栈区和堆区
一.代码区:存放函数体的二进制代码,由操作系统进行管理
存放的就是CPU执行的机器指令
代码区有两个特点
1.共享性
目的是对于频繁被执行的程序,只需在内存中有一份代码即可。
2.只读性
防止程序在执行时被意外的修改,造成不必要的麻烦
二,全局区:存放全局变量和静态变量以及常量
存放的内容有全局变量和静态变量和常量(字符串常量,const 修饰的全局变量(全局常量))
三,栈区和堆区
我之所以将这两个概念放在一起讲,一方面是因为我们之前就已经对这些概念有过了解,我们也经常接触这些东西,而且二者比较相近,经常放在一起解释了解
1.定义:堆栈是一种数据结构,具体是一个特定的存储区域或寄存器,是一种数据项排列的数据结构
2.栈区:
1)只能在一端(栈顶)对数据项进行插入和删除等操作,但另一端是固定的,栈顶是浮动的,(先进后去)
2)栈顶地址总是小于栈的基地址。
3)存放自动变量,函数执行结束后这些存储单元由编译器自动释放
4)由系统自动分配空间
3.堆区(自由存储区)
1)在运行的时候调用程序分配内存可以在任何时候决定分配内存即分配的大小,用户自行决定在何时释放。堆中的所有东西都是匿名的,不能用名字访问,只能通过指针来访问。
2)需要程序员自己申请,并指明大小
p=(char*)malloc(10)
例如上面的式子,在堆区中开辟了一片空间,用指针p指向这一片空间,p是在栈区中的,申请得来的空间在堆区。
四,使用new开辟空间
格式:数据类型 new int(数据)
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int * test() { //在堆区创建一块空间,存放一个整型数据 int* p = new int(20); return p; } int* test1() { //存放一个数组,数组中含十个整型 int* p = new int[10]; for (int i = 0; i < 10; i++) { p[i] = i; } return p; } int main() { int* p = test(); int* pc = test1(); cout << *p << endl; for (int i = 0; i < 10; i++) { cout << pc[i] << endl; } //二者的释放形式不同,要注意 delete[]pc; delete p; return 0; }
二,引用
一,引用的定义和本质
1.引用就是给变量起别名,可以把它理解为指针,因为它的本质就是一个指针常量(指向不能变)
2.定义格式:数据类型 &别名=原名;
3.特点
1)引用必须初始化
2)引用在初始化后,不可以改变
4.引用的本质
本质就是指针常量,只是所有指针的操作都由编译器做了而已
补:指针常量和常量指针
前者是指针的指向不能改变,本质是一个常量,常量的特性体现在了指针上,也就是指针的指向。
后者是指针的值不能改变,本质是一个指针,指针指向的变量体现了常量的特性,值不能改变,例如数组。
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int main() { int a = 10; int& b = a; int& c = a; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; int d = 20; b = d;//赋值操作,并不是更改引用的操作 cout <<'\n' << "d = " << d << endl; cout << "b = " << b << endl; return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; void fun(int& ref) { //ref是引用,自动转换为*ref=100; ref = 100; } int main() { int a = 10; //自动转换为int const* ref=&a;指针常量的指向不可变,也说明了引用初始化之后为什么不能改变 int& ref = a; //自动转换为*ref=20; ref = 20; cout << "a = " << a << endl; cout << "ref = " << ref << endl; fun(a); cout << "ref = " << ref << endl; return 0; }
二,引用做函数参数
函数传参时,可以利用引用的技术让形参改变实参
优点:可以简化指针
修改实参
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; //交换函数 //1.值传递 void swap1(int a, int b) { int temp = a; a = b; b = temp; } //2.地址传递 void swap2(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } //3.引用传递 void swap3(int& a, int& b) { int temp = a; a = b; b = temp; } int main() { int a = 10; int b = 20; swap1(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; swap2(&a, &b); putchar('\n'); cout << "a = " << a << endl; cout << "b = " << b << endl; swap1(a, b); swap3(a, b); putchar('\n'); cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; } //我认为,引用就是类似于指针的一个概念。
三,引用做函数的返回值
注意:
1.不要返回局部变量的引用
这就是一个普通的野指针问题,你要是不懂的话就说明基础还不行呀!要努力了
2.函数的调用可以作为左值
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int& test() { static int a = 10; return a; } int main() { int& b = test(); cout << b << endl; //以引用为函数返回值的函数的调用可以作为左值 //像此处的函数返回值是一个引用,本质上还是一个int型的变量,所以可以作为左值使用。 test() = 1000; cout << b << endl; return 0; }
左值必须是一个可以修改的变量。
四,常量引用
差点忘了这个,嘿嘿嘿
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int main() { int a = 10; //等价于int temp=10; const int &ref=temp; //temp为系统帮取的变量名,也不一定是temp; //加上const之后,变量的值就不能被修改了,变为了只读状态 const int& ref = 10; //引用需要一个合法的内存空间 //int& ref = 10;例如这样的操作就是错误的 return 0; }
什么叫常量引用,就是给一个常量起别名,但是这样做是不行的,为什么加上const就对了?
加上const后,这个常量的指向就是一定的,之前为什么不行?就是因为指向不定,编译器不懂这个常量到底是哪里来的,加上const后,编译器会自动给这个常量分配一个空间,以支持语法的正确性。
三,函数的高级操作
一,函数默认参数
1)如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须有默认参数
2)如果函数声明有默认参数,函数实现就不能有默认参数
声明和实现只能有一个阶段有默认参数
不然编译器会报错,变量不能反复定义
**正确形式如下:
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; int func(int a, int b=2, int c=3) { return a + b + c; } int main() { cout << func(1) << endl; return 0; } //1.如果某个位置已经有了默认参数,那么从这个位置开始往后,从左往右都必须有默认值 //2.如果函数声明有默认参数,函数实现就不能有默认参数,不然就会产生二义性 //即对变量实现了多次定义。(声明和实现只能有一个默认参数)
二,函数占位参数
函数占位参数,顾名思义,就是用来占位的参数,调用函数时必须填补该位置
函数占位参数也能是函数默认参数
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; void func(int a, int) { cout << "this is function" << endl; } int main() { int a = 10; func(a,10); return 0; } //函数占位参数也可以有默认参数 void func(int a, int = 10) { cout << "this is function" << endl; } int main() { int a = 10; func(a); return 0; }
三,函数重载
函数名可以相同,用来提高复用性(我现在也不知道这样有啥用,后面慢慢学)
1.条件
1)在同一个作用域下
2)函数名相同
3)函数参数类型不同/顺序不同/个数不同
这段主函数部分代码不完整,是留给你们自己调试证明的
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; void func() { cout << "this is func" << endl; } void func(int a) { cout << "this is func(int a)" << endl; } void func(double a) { cout << "this is func(double a)" << endl; } void func(int a,double b) { cout << "this is func(int a,double b)" << endl; } void func(double a,int b) { cout << "this is func(double a,int b)" << endl; } int main() { func(); return 0; }
注意事项:
函数的返回值不可以作为函数重载的条件
即,仅仅只有返回值不同,其他的条件都相同,这样是不算函数重载的
无法重载仅按返回类型区分的函数
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; //1.引用作为重载条件 void func(int& a) { cout << "this is func(int& a) " << endl; } void func(const int& a) { cout << "this is func(const int& a) " << endl; } //2.函数重载碰到函数默认参数 void func1(int a, int b = 10) { cout << "this is func1(int a, int b = 10)" << endl; } void func1(int a) { cout << "this is func(int a) " << endl; } int main() { func(10); return 0; }
那这是为什么呢?
嘿嘿嘿,我们来分析一些,我们既然写了这个函数,是不是一定会去使用它,那么当我们使用的时候会写该函数的返回类型吗?当然不会啦,那让编译器怎么办?这不是为难人家吗?哈哈哈
2.特殊情况
1)引用作为重载条件
2)函数重载碰到函数默认参数(尽量不要让他们相遇哦,会出错的
好了,今天的学习就到这里了,嘿嘿嘿,你们学到了多少呢?加油哦,别放弃哦!!!