既然找不到实习那就开始学习C++吧!?Day01-Part1
1.作用域运算符::
作用域运算符可以用来解决局部变量与全局变量的重名问题
//全局变量
int a = 10;
//1. 局部变量和全局变量同名
void test(){
int a = 20;
//打印局部变量a
cout << "局部变量a:" << a << endl;
//打印全局变量a
cout << "全局变量a:" << ::a << endl;
}
2.namespace
更好的控制标识符的作用域
1.创建一个命名空间
namespace a{
int a = 10;
}
namespace b{
int a = 20;
}
//1. 局部变量和全局变量同名
void test(){
int a = 20;
//打印局部变量a
cout << "a:" << a::a << endl;
//打印全局变量a
cout << "b:" << b::a << endl;
}
2.命名空间只能在全局范围内定义
3.命名空间可嵌套命名空间
namespace A{
int a = 10;
namespace B{
int a = 20;
}
}
void test(){
cout << "A::a : " << A::a << endl;
cout << "A::B::a : " << A::B::a << endl;
}
4.命名空间是开放的,即可以随时把新的成员加入已有的命名空间中
namespace a{
int a = 10;
}
namespace b{
int a = 20;
}
namespace a{
void testA(){
cout << "testa:" << 30 << endl;
}
}
//1. 局部变量和全局变量同名
void test(){
int a = 20;
//打印局部变量a
cout << "a:" << a::a << endl;
a::testA();
//打印全局变量a
cout << "b:" << b::a << endl;
}
5.声明和实现可分离
namespace a{
int a = 10;
}
namespace b{
int a = 20;
}
namespace a{
void testA();
}
void a::testA(){
cout << "testa:" << 30 << endl;
}
//1. 局部变量和全局变量同名
void test(){
int a = 20;
//打印局部变量a
cout << "a:" << a::a << endl;
a::testA();
//打印全局变量a
cout << "b:" << b::a << endl;
}
6.无名命名空间,意味着命名空间中的标识符只能在本文件内访问,相当于给这个标识符加上了static,使得其可以作为内部连接
7.命名空间别名
namespace a{
int a = 10;
}
namespace b{
int a = 20;
}
namespace a{
void testA();
}
void a::testA(){
cout << "testa:" << 30 << endl;
}
//1. 局部变量和全局变量同名
void test(){
namespace A = a;
//打印局部变量a
cout << "a:" << A::a << endl;
A::testA();
//打印全局变量a
cout << "b:" << b::a << endl;
}
3.using
1.using声明可使得指定的标识符可用。
namespace a{
int a = 10;
}
namespace b{
int a = 20;
}
namespace a{
void testA();
}
void a::testA(){
cout << "testa:" << 30 << endl;
}
//1. 局部变量和全局变量同名
void test(){
using a::a;
//打印局部变量a
cout << "a:" << a << endl;
a::testA();
//打印全局变量a
cout << "b:" << b::a << endl;
}
2.如果命名空间包含一组用相同名字重载的函数,using声明就声明了这个重载函数的所有集合。
namespace A{
void func(){}
void func(int x){}
int func(int x,int y){}
}
void test(){
using A::func;
func();
func(10);
func(10, 20);
}
3.using编译指令使整个命名空间标识符可用.
namespace A{
int paramA = 20;
int paramB = 30;
void funcA(){ cout << "hello funcA" << endl; }
void funcB(){ cout << "hello funcB" << endl; }
}
void test01(){
using namespace A;
cout << paramA << endl;
cout << paramB << endl;
funcA();
funcB();
//不会产生二义性
int paramA = 30;
cout << paramA << endl;
}
namespace B{
int paramA = 20;
int paramB = 30;
void funcA(){ cout << "hello funcA" << endl; }
void funcB(){ cout << "hello funcB" << endl; }
}
void test02(){
using namespace A;
using namespace B;
//二义性产生,不知道调用A还是B的paramA
//cout << paramA << endl;
}
4.const
1.用来限定一个变量不允许改变,它将一个对象转换成一个常量。
2.在c++中,一个const不必创建内存空间,而在c中,一个const总是需要一块内存空间。
3.c++中全局const变量存储在只读数据段,不可修改。
4.c++局部const
-
对于基础数据类型,也就是const int a = 10这种,编译器会把它放到符号表中,不分配内存,当对其取地址时,会分配内存。
const int constA = 10; int* p = (int*)&constA; *p = 300; cout << "constA:" << constA << endl; cout << "*p:" << *p << endl;
-
对于基础数据类型,如果用一个变量初始化const变量,如果const int a = b,那么也是会给a分配内存。
int b = 10; const int constA = b; int* p = (int*)&constA; *p = 300; cout << "constA:" << constA << endl; cout << "*p:" << *p << endl;
-
对于自定数据类型,比如类对象,那么也会分配内存。
const Person person; //未初始化age //person.age = 50; //不可修改 Person* pPerson = (Person*)&person; //指针间接修改 pPerson->age = 100; cout << "pPerson->age:" << pPerson->age << endl; pPerson->age = 200; cout << "pPerson->age:" << pPerson->age << endl;
5.尽量以const替换#define
5.reference
1.引用是c++对c的重要扩充。
2.基本语法Type& ref = val;
-
&在此不是求地址运算,而是起标识作用。
-
类型标识符是指目标变量的类型
-
必须在声明引用变量时进行初始化。
-
引用初始化之后不能改变。
-
不能有NULL引用。必须确保引用是和一块合法的存储单元关联。
-
可以建立对数组的引用。
//1. 认识引用 void test01(){ int a = 10; //给变量a取一个别名b int& b = a; cout << "a:" << a << endl; cout << "b:" << b << endl; cout << "------------" << endl; //操作b就相当于操作a本身 b = 100; cout << "a:" << a << endl; cout << "b:" << b << endl; cout << "------------" << endl; //一个变量可以有n个别名 int& c = a; c = 200; cout << "a:" << a << endl; cout << "b:" << b << endl; cout << "c:" << c << endl; cout << "------------" << endl; //a,b,c的地址都是相同的 cout << "a:" << &a << endl; cout << "b:" << &b << endl; cout << "c:" << &c << endl; } //2. 使用引用注意事项 void test02(){ //1) 引用必须初始化 //int& ref; //报错:必须初始化引用 //2) 引用一旦初始化,不能改变引用 int a = 10; int b = 20; int& ref = a; ref = b; //不能改变引用 //3) 不能对数组建立引用 int arr[10]; //int& ref3[10] = arr; } //1. 建立数组引用方法一 typedef int ArrRef[10]; int arr[10]; ArrRef& aRef = arr; for (int i = 0; i < 10;i ++){ aRef[i] = i+1; } for (int i = 0; i < 10;i++){ cout << arr[i] << " "; } cout << endl; //2. 建立数组引用方法二 int(&f)[10] = arr; for (int i = 0; i < 10; i++){ f[i] = i+10; } for (int i = 0; i < 10; i++){ cout << arr[i] << " "; } cout << endl;
3.函数中的引用
最常见看见引用的地方是在函数参数和返回值中。当引用被用作函数参数的时,在函数内对任何引用的修改,将对还函数外的参数产生改变。当然,可以通过传递一个指针来做相同的事情,但引用具有更清晰的语法。
如果从函数中返回一个引用,必须像从函数中返回一个指针一样对待。当函数返回值时,引用关联的内存一定要存在。
void swap1(int a, int b){
int t = a;
a = b;
b = t;
}
void swap2(int* a, int* b){
int t = *a;
*a = *b;
*b = t;
}
void swap3(int& a, int& b){
int t = a;
a = b;
b = t;
}
void test(){
int a = 10;
int b = 20;
swap1(a, b);
cout << "a:" << a << "b:" << b << endl;
//swap2(&a, &b);
//cout << "a:" << a << "b:" << b << endl;
swap3(a, b);
cout << "a:" << a << "b:" << b << endl;
}
通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单:
- 函数调用时传递的实参不必加“&”符
- 在被调函数中不必在参数前加“*”符
引用作为其它变量的别名而存在,因此在一些场合可以代替指针。C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。
//返回局部变量引用
int& TestFun01(){
int a = 10; //局部变量
return a;
}
//返回静态变量引用
int& TestFunc02(){
static int a = 20;
cout << "static int a : " << a << endl;
return a;
}
int main(){
//不能返回局部变量的引用
int& ret01 = TestFun01();
//cout << "ret01 : " << ret01 << endl; //报错
//如果函数做左值,那么必须返回引用
TestFunc02();
TestFunc02() = 100;
TestFunc02();
return EXIT_SUCCESS;
}
4.引用的本质
- 引用的本质是在c++内部实现是一个指针常量.
- c++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。
Type& ref = val; // Type* const ref = &val;
//发现是引用,转换为 int* const ref = &a;
void testFunc(int& ref){
ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
int a = 10;
int& aRef = a; //自动转换为 int* const aRef = &a;这也能说明引用为什么必须初始化
aRef = 20; //内部发现aRef是引用,自动帮我们转换为: *aRef = 20;
cout << "a:" << a << endl;
cout << "aRef:" << aRef << endl;
testFunc(a);
return EXIT_SUCCESS;
}
5.指针引用
使用别名可以更方便的改变一个指针的指向。
struct Teacher{
int mAge;
};
//指针间接修改teacher的年龄
void AllocateAndInitByPointer(Teacher** teacher){
*teacher = (Teacher*)malloc(sizeof(Teacher));
(*teacher)->mAge = 200;
}
//引用修改teacher年龄
void AllocateAndInitByReference(Teacher*& teacher){
teacher->mAge = 300;
}
void test(){
//创建Teacher
Teacher* teacher = NULL;
//指针间接赋值
AllocateAndInitByPointer(&teacher);
cout << "AllocateAndInitByPointer:" << teacher->mAge << endl;
//引用赋值,将teacher本身传到ChangeAgeByReference函数中
AllocateAndInitByReference(teacher);
cout << "AllocateAndInitByReference:" << teacher->mAge << endl;
free(teacher);
}
6.常量引用
-
常量引用常见格式
const Type& ref = val;
-
字面量不能赋给引用,但是可以赋给const引用
-
const修饰的引用,不能修改。
void test01(){ int a = 100; const int& aRef = a; //此时aRef就是a //aRef = 200; 不能通过aRef的值 a = 100; //OK cout << "a:" << a << endl; cout << "aRef:" << aRef << endl; } void test02(){ //不能把一个字面量赋给引用 //int& ref = 100; //但是可以把一个字面量赋给常引用 const int& ref = 100; //int temp = 200; const int& ret = temp; }
-
常量引用主要用在函数的形参,尤其是类的拷贝/复制构造函数。(防止函数中意外修改数据)
void ShowVal(const int& param){ cout << "param:" << param << endl; }
6.内联函数
1.内联函数基本概念
-
在c++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。
注:内联函数调用仅仅是对编译器的一个内联建议,编译器是否采取内联函数的建议取决于内联函数是否符合内联的有利条件。如果函数体特别大,那么编译器将会忽略编译器的内联建议,将内联函数当作普通函数处理。
例如:
int root(int n) { return (int)sqrt((float)n); } int prime(int n) { int i; for (i = 2; i <= root(n); i++) { if (n%i == 0) return 0; return 1; } }
如果不使用内联函数,调用root函数时,程序会重复保存当前函数的执行现场,将函数中的局部变量以及函数地址压入堆栈,然后再将即将调用的新函数加载到内存中,这要经历复制参数值、跳转到所调用函数的内存位置、执行函数代码、存储函数返回值等过程,当函数执行完后,再获取之前正在调用的函数的地址,回去继续执行那个函数,运行时间开销简直太多了。
-
n 在普通函数(非成员函数)函数前面加上inline关键字使之成为内联函数。但是必须注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。
inline void func(int a);//无效声明 inline int func(int a){return ++;}//应该与声明一同写
2.类内部的内联函数
- 为了定义内联函数,通常必须在函数定义前面放一个inline关键字。但是在类内部定义内联函数时并不是必须的。任何在类内部定义的函数自动成为内联函数。
7.函数的默认参数
C++在声明函数原型的时候可为一个或多个参数指定默认的参数值,函数调用时如果没有指定这个值,就会调用默认值。
注:
- 函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。
- 如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认参数
void TestFunc01(int a = 10, int b = 20){
cout << "a + b = " << a + b << endl;
}
//注意点:
//1. 形参b设置默认参数值,那么后面位置的形参c也需要设置默认参数
void TestFunc02(int a,int b = 10,int c = 10){}
//2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数
void TestFunc03(int a = 0,int b = 0);
void TestFunc03(int a, int b){}
int main(){
//1.如果没有传参数,那么使用默认参数
TestFunc01();
//2. 如果传一个参数,那么第二个参数使用默认参数
TestFunc01(100);
//3. 如果传入两个参数,那么两个参数都使用我们传入的参数
TestFunc01(100, 200);
return EXIT_SUCCESS;
}
8.函数的占位参数
c++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。(操作符重载时使用)
void TestFunc01(int a,int b,int){
//函数内部无法使用占位参数
cout << "a + b = " << a + b << endl;
}
//占位参数也可以设置默认值
void TestFunc02(int a, int b, int = 20){
//函数内部依旧无法使用占位参数
cout << "a + b = " << a + b << endl;
}
int main(){
//错误调用,占位参数也是参数,必须传参数
//TestFunc01(10,20);
//正确调用
TestFunc01(10,20,30);
//正确调用
TestFunc02(10,20);
//正确调用
TestFunc02(10, 20, 30);
return EXIT_SUCCESS;
}
9.函数重载
1.实现函数重载的条件
- 同一个作用域
- 参数个数不同
- 参数类型不同
- 参数顺序不同
注:函数返回值不作为重载条件
//1. 函数重载条件
namespace A{
void MyFunc(){ cout << "无参数!" << endl; }
void MyFunc(int a){ cout << "a: " << a << endl; }
void MyFunc(string b){ cout << "b: " << b << endl; }
void MyFunc(int a, string b){ cout << "a: " << a << " b:" << b << endl;}
void MyFunc(string b, int a){cout << "a: " << a << " b:" << b << endl;}
}
//2.返回值不作为函数重载依据
namespace B{
void MyFunc(string b, int a){}
//int MyFunc(string b, int a){} //无法重载仅按返回值区分的函数
}
注:函数重载和默认参数一起使用,需要注意二义性的问题
void Myfunc(string b){
cout << b << endl;
}
void Myfunc(string b, int a = 10){
cout << b << a << endl;
}
int main(){
Myfunc("hello myFunc");// 存在二义性问题
return EXIT_SUCCESS;
}
2.函数重载实现原理
编译器为了实现函数重载,会使用不同的参数来修饰不同的函数名,产生不同的内部名。
void func(){}
void func(int x){}
void func(int x,char y){}
//不同编译器会产生不同的内部名
_Z4funcv //v 代表void,无参数
_Z4funci //i 代表参数为int类型
_Z4funcic //i 代表第一个参数为int类型,第二个参数为char类型