目录
1. 命名空间 ::
namespace A{ //A 是空间的名字
int a;
void func()
{}
}
(1)命名空间只能写在全局;
(2)可以嵌套;
namespace Aspace {
int a = 1;
namespace B {
int b;
}
}
(3)命名空间是开放的,可以随时加入新成员;
(4)命名空间可以取别名;
namespace newSpace = oldSpace;
newSpace:新名字
oldSpace:旧名字
(5)分文件编写代码时,如果.h中有两个命名空间,但是里边的成员函数或变量重名时,在.cpp中实现函数时需要加上命名空间。eg:
test.h:
#pragma once
#include <iostream>
using namespace std;
namespace Space1
{
void func();
}
namespace Space2
{
void func();
}
test.c:
#include "test.h"
void Space1 :: func() //需要在函数名之前加上命名空间的名字
{
cout << "func" << endl;
}
(6)作用域运算符 ::
eg:
#include <iostream>
using namespace std; //标准命名空间
namespace Aspace {
int aa = 1;
void func()
{
}
namespace B {
int b = 3;
}
}
namespace Cspace
{
int c = 2 ;
}
int aa = 100;
int main()
{
int aa = 10;
cout << "aa =" << aa << endl; //就近原则,打印局部变量aa的值
cout << "::aa =" << ::aa << endl; //如果::前边没有东西,则表示取全局的作用域,打印全局变量的aa
cout << "Cspace::c =" << Cspace::c << endl; //::前边加上命名空间的名字则打印命名空间中对应成员的值
cout << "Aspace::B::b =" << Aspace::B::b << endl;
system("pause");
return EXIT_SUCCESS;
}
输出为:
aa =10
::aa =100
Cspace::c =2
Aspace::B::b =3
2. using 声明
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
namespace A {
int a = 1;
int b = 2;
int c = 3;
}
void test01()
{
//using声明是让命名空间中某个标识符可以直接使用
using A::a;
cout << a << endl;
int a = 50; //错误,因为using声明了某个变量,在该作用域内不能定义同名的变量
}
void test02()
{
//using 编译指令,让某个命名空间中的标识符都可以直接使用
using namespace A;
cout << a << endl;
int a = 10; //这里没错,因为这里类似于命名空间中的a为全局变量,而这个a是局部变量
}
3. 结构体加强
(1)不需要加struct就可以定义变量
struct Maker
{
char name[64];
int age;
};
void test()
{
Maker a;
}
(2)结构体内可以写函数
struct Maker2
{
int a;
void func()
{
cout << "func" << endl;
}
};
void test()
{
Maker2 a2;
a2.func();
}
4. 不能进行隐式转换,必须是显式转换
void test()
{
char* p = (char*)malloc(64);
}
5. 三目运算符
(1)C语言中,三目运算符返回的是右值,不能被赋值;
void test()
{
int a = 10;
int b = 20;
printf("%d",a > b ? a : b);
//(a > b ? a : b) = 100; //err
}
但是,如果想改变三目运算符结果的值,则可以这样子写:
*(a > b ? &a : &b) = 100;
(2)C++中,三目运算符返回的是左值,是空间,可以被赋值;
void test()
{
int a = 10;
int b = 20;
(a > b ? a : b) = 100;
cout<<"a ="<<a<<endl;
cout<<"b ="<<b<<endl;
}
输出为:
a=10;
b=100;
左值和右值的概念:
左值:Lvalue,代表location,表示内存可以寻址,可以赋值;
右值:Rvalue,代表read,表示可以知道它的值。
6. C/C++中的const
C | C++ |
---|---|
const修饰的全局变量会被存储在常量区,只要有定义即分配内存空间,不能修改,也不能间接修改 | 全局const当声明extern或者对变量取地址时,编译器才会分配存储地址,变量存储在只读数据段 |
const修饰的局部变量存储在堆栈区,不能直接改变变量的值,可以通过指针间接修改const的值 | 只有在分配了内存后(此时编译器不会进行优化),才能通过指针间接修改const变量的值。 ① 对于const int a = 10这种,编译器会进行优化,将a替换为10,类似于#define;(如果不想让编译器进行优化,可以在定义变量时加上volatile) ② 如果用一个变量初始化const,比如const int a = b,那么也会给a分配内存; ③ 对于类对象,也会分配内存,如下代码所示。 |
默认为外部连接,当两个c文件中都有同样的const变量时,编译器会报重定义错误;使用别的文件的全局const需要加extern。 | 默认为内部连接,只能在本文件使用,重名的const变量不会报重定义错误。如果想让C++中的const有外部链接,必须在定义时就加上extern,如:extern const int a = 10; |
(1) 对于基础数据类型,也就是const int a = 10;这种,编译器会进行优化,直接用值替换。
const int a = 10;
int* p = (int*)&a;
*p = 20;
cout << "a = "<< a << endl; //编译器在编译阶段把这里优化了,等价于:cout<<"a="<<10<<endl;
cout << "*p = "<< *p << endl;
输出为:
a = 10
*p = 20
(2)对于基础数据类型,如果用一个变量去给const修饰的变量赋值,那么也会给const变量分配内存,编译器就不会进行优化了。
int b = 10;
const int a= b;
int* p = (int*)&a;
*p = 300;
cout << "a = " << a << endl;
cout << "*p = " << *p << endl;
输出为:
a = 300
*p = 300
(3)对于自定义数据类型,比如类对象,也会分配内存,编译器不能进行优化。
const Person person; //未初始化age
//person.age = 50; //不可修改
//指针间接修改
Person* p = (Person*)&person;
p->age = 100;
尽量用const替代#define。
(1)#define没有数据类型,const修饰的变量有数据类型,可以进行数据类型检查。
#define MA 128
const short ma = 128;
void func(short a)
{
cout << "func(short a)" << endl;
}
void func(int a)
{
cout << "func(int a)" << endl;
}
int main()
{
func(MA); //传入MA时,只能调用到func(int a);
func(ma); //传ma时,就会进行类型检查了
system("pause");
return EXIT_SUCCESS;
}
(2)const修饰的变量有作用域,#define不重视作用域,不能限定常量的使用范围。
7. 引用(重难点)
C++ 增加了另外一种给函数传递地址的途径,即按引用传递。
- 变量名实质上是一段连续内存空间的别名;
- 程序中通过变量来申请并命名内存空间;
- 通过变量名可以使用内存空间。
(1)引用可以作为一个已定义变量的别名
void test02()
{
int a = 10;
int& b = a; //给a的空间取别名叫b
b = 100;
cout << a << endl;
}
输出为:100
而指针的写法是:
void test02()
{
int a = 10;
int* b = &a;
*b = 100;
}
(2)当定义函数时,按引用传递
void func(int &b)
{
b = 100;
}
void test()
{
int a = 10;
func(a);
cout << "a = " << a << endl;
}
输出为:a = 100
而C指针的写法是:
void func(int *b)
{
*b = 100;
}
void test()
{
int a = 10;
func(&a);
cout << "a = " << a << endl;
}
引用的注意:
- ① & 在此不是求地址运算,而是起标识作用;
- ② 引用创建时必须初始化;(还有,创建常量时也需要初始化)
int& a; //err
- ③ 引用一旦初始化后不能改变它的指向;
int a =10; int b = 20; int& c = a; //给a的空间取别名为c c = b; //err,不能改变引用
- ④ 必须引用一块合法的内存空间;
(3)建立数组引用
int main()
{
int arr[] = { 1,2,3 };
//第一种方法
typedef int(Arr)[3]; //定义数组类型
Arr &arref = arr; //建立引用
//第二种方法,直接定义引用
int(&arref2)[3] = arr;
for (int i = 0; i < 3; i++)
{
cout << arref[i] << endl;
}
}
(4)引用的本质
C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。即,引用的本质在C++内部实现是一个常指针。
int a = 10;
int& aRef = a; //自动转换为int* const aRef = &a;这也能说明引用为什么必须初始化
aRef = 20; //内部发现aRef 是引用,自动帮我们转换为: *aRef = 20;
如下图所示:
(5)指针引用(重、难)
在C语言中,如果想改变一个指针的指向,而不是它所指向的内容,则会使用二级指针,如下:
void func(char** tmp)
{
char* p = (char*)malloc(64);
memset(p, 0, 64);
strcpy(p, "hello");
*tmp = p;
}
void test()
{
char* mp = NULL;
func(&mp);
cout << mp << endl;
}
改成引用的话:
void func(char* &tmp)
{
char* p = (char*)malloc(64);
memset(p, 0, 64);
strcpy(p, "hello");
tmp = p;
}
void test()
{
char* mp = NULL;
func(mp);
cout << mp << endl;
}
(6)常量引用
定义格式:
const int& ref = val;
int main()
{
//普通引用
int a = 10;
int& tmp = a;
tmp = 20;
//int &ref = 10; err,不能给字面量取别名
const int& tmp1 = 10; //可以给const修饰的引用赋予字面量
//编译器会把上边的代码变为:int x=10; const int &tmp1=x;
tmp1 = 20; //err,不能修改
}
(7)引用的使用
常量引用主要用在函数的形参,尤其是类的拷贝构造函数。
将函数的形参定义为常量引用的好处:
- 引用不产生新的变量,减少形参与实参传递时的开销;
- 由于引用可能导致实参随形参而改变,将其定义为常量引用可以消除这种副作用;
- 如果希望实参随着形参的改变而改变,那么使用一般的引用;如果不希望实参改变,那么使用常引用。
① 引用作为函数的参数
void func(int& a, int& b)
{
int sum = a + b;
cout << "sum = " << sum << endl;
}
void test()
{
int a = 10;
int b = 20;
func(a, b);
}
通过引用参数产生的效果同按地址传递是一样的。引用的语法更简单:
- 函数调用时传递的实参不必加“ & ”;
- 在被调函数中不必在参数前加“ * ”;
② 引用作为函数的返回值
如果从函数中返回一个引用,必须像从函数中返回一个指针一样对待。当函数返回时,引用关联的内存一定要存在。
int& func()
{
int b = 10; //不能返回局部变量的引用
int& p = b;
return p;
}
int& func2()
{
static int b = 10;
return b;
}
void test()
{
cout << "func=" << func() << endl;
func() = 100; //如果要函数当左值,那么该函数必须返回引用
cout << "func2()=" << func2() << endl;
}
8. 内联函数
内联函数:
① 具有普通函数的所有行为。
② 会在适当地方向预定义宏一样展开,所以不需要函数调用时的开销。
③ 内联函数也占用空间,但是相对于普通函数,只是省去了函数调用时的压栈、跳转、返回的开销,可以理解为内联函数是“以空间换时间”。
④ 在普通函数前加上 inline 关键字使之成为内联函数。但是必须注意,函数体和声明必须结合在一起,否则编译器将它作为普通函数来对待。
inline void func(int a); //该写法没有任何效果
类内部的内联函数:
类内部定义内联函数时并不必须要加 inline,任何在类内部定义的函数自动成为内联函数。
c++内联编译会有一些限制:
- 不能存在任何形式的循环语句;
- 不能存在过多的条件判断语句;
- 函数体不能过于庞大;
- 不能对函数进行取址操作;
9. 函数的默认参数
函数的默认参数的作用:
当函数内常用到形参的某个值,但偶尔要使用其他值; 增加函数的灵活性。
int func(int a, int b = 0) //b=0即为b的默认值
{
return a + b;
}
void test()
{
cout << func(1,2) << endl;
cout << func(1) << endl;
}
注意:
- 函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。
- 函数声明和函数定义不能同时设置默认参数。
10. 函数的占位参数
void func(int a, int b, int)
{
//函数内部无法使用占位参数
cout << "a+b = " << a + b << endl;
}
//占位参数也可以设置默认值
void func2(int a, int b, int=20)
{
//函数内部依旧无法使用占位参数
cout << "a+b = " << a + b << endl;
}
int main()
{
func(10,20); //err,占位参数也是参数,必须传参数
func(10,20,30); //ok
func2(10,20); //ok
func2(10,20,30); //ok
return EXIT_SUCCESS;
}
后边讲的操作符重载的后置++要用到这个。
11. 函数传参的三种方式
值传递、指针传递、引用传递
//值传递
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
//指针传递
void swap2(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//引用传递
void swap3(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
}
void myprint(int& a, int& b)
{
cout << "a=" << a << "b=" << b << endl;
}
int main()
{
int a = 10;
int b = 20;
swap(a,b);
myprint(a, b);
swap2(&a,&b);
myprint(a, b);
swap3(a,b);
myprint(a,b);
}
上述三种交换结果,只有第一种值传递无法交换a,b的值。
12. 创建一个学生类
class Student
{
public: //公有
void setName(string Name) //成员方法,也叫成员函数
{
name = Name;
}
void setID(int Id)
{
id = Id;
}
void myprint()
{
cout << "姓名:" << name << "\n学号:" << id << endl;
}
private: //私有权限
string name; //成员属性
int id;
};
int main()
{
Student s;
s.setName("Paul");
s.setID(1);
s.myprint();
system("pause");
return EXIT_SUCCESS;
}