语法格式
头文件与宏定义
# include<头文件名>
# define PI 3.1415
引用
int a = 10;
int &b = a;
int c = 20;
b = c;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
结果: a = 20 b = 20 c = 20 可见引用操作相当于地址操作。
引用传参
引用作为函数参数传递称为引用传递,引用传递与地址传递一样会改变实参
引用作为函数的返回。
int & test01()
{
int a = 10;
return a;
}
int & test02()
{
static int a = 10;
return a;
}
int main()
{
int &ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
}
结果: ref2 = 10 ref2 = 10 ref2 = 1000 ref2 = 1000 流程 : ref2 引用全局变量,因此前边两个返回值为10 test02() = 1000 修改全局变量中的ref2的值为1000,main中的ref2值随之改变。 注:这里的引用是main中的ref2引用了方法中ref2,相当于别名与原名相同。
引用的本质
引用的本质在C++内部实现是一个指针常量 即指针中的常量,指向不可改,指针指向的内容可以修改。 这也是为什么引用初始化后不可以再修改的原因,而引用是可以赋值的,即对应指针常量指向的内容可以修改。
常量引用
void showValue(const int &val)
{
cout << "val = " << val << endl;
}
int main()
{
int a = 100;
showValue(a);
return 0;
}
函数提高
函数默认值
C++中函数函数是可以有默认参数的
int func(int a, int b = 20, int c = 30)
{
return a + b + c;
}
注意事项
- 如果某个位置有了默认参数,那么这个位置往后都必须有默认值
- 函数声明和函数实现只能有一个有默认值,否则会导致编译器混淆默认值的使用。
函数占用参数
占用参数也可以有默认值
void func(int a, int)
{
cout << "this if func" << endl;
}
函数重载
作用:函数名可以相同,提高复用性
函数重载满足的条件:
- 函数作用域相同
- 函数名称相同
- 函数参数类型,个数,或者顺序不同
注:函数返回值不可以作为函数重载的条件
函数重载的注意事项 4. 当函数重载碰到默认参数,则可能出现二义性,会报错,尽量不使用 5. 加const和不加const可以作为函数重载的条件
类与对象
访问权限
一个类包含属性和行为,属性和行为都可以称为类的成员。
- public 成员类内可以访问,类外也可以访问
- protected 成员类内可以访问,类外不可访问,儿子可以访问父亲中的保护内容
- private 成员类内可以访问,类外不可访问,儿子可以访问父亲中的私有内容
C++中struct的默认权限的公共,class的默认权限是私有
构造函数和析构函数
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
构造函数语法 类名(){} 1.构造函数没有返回值也不写void 2.函数名称与类名相同 3. 构造函数可以由参数,因此可以发生重载 4. 程序在调用时自动调用构造函数,无需手动调用,且只会被构造一次。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。 析构函数语法:~类名(){} 5. 析构函数,没有返回值也不写void 6. 函数名称与类名相同在名称前加上符号~ 7. 析构函数不可以有参数,因此不能发生重载 8. 程序在对象销毁前自动调用析构函数,无需手动调用,且只会调用一次 。
构造和析构都是类必须有的方法,如果不写编译器会提供它们的空实现。
构造函数的分类和调用
按照参数分类,分为无参构造(默认构造是无参的)和有参构造。 按照类型分类,分为普通构造和拷贝构造。 拷贝构造需要将对象本身以引用的方式传递参数,且不可改变(以const修饰)
Person (const Person &p )
{
age = p.age;
}
构造函数的调用方式
- 括号法
调用有参构造函数时使用,无参构造时不需要加括号,以避免编译器将调用误识别为函数的声明。
Person p1;
Person p2(10);
Person p3(p2);
- 显示法
Person p1;
Person p2 = Person(10);
Person p3 = Person(p2);
- 隐式转换法
Person p4 = 10;
拷贝函数调用的时机
C++调用拷贝函数的时机
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的时候给函数参数传值(即实参向形参赋值的时候)
- 值的方式返回局部对象(即作为函数的返回值返回时)
构造函数的调用规则
默认情况下,C++编译器在创建一个类的时候会至少添加三个函数,默认构造,默认析构和拷贝构造。 如果用户定义了有参构造,则不会在添加默认构造,但仍然会添加拷贝构造,如果用户定义了拷贝构造,则不会添加默认构造和拷贝构造。
深拷贝和浅拷贝
深浅拷贝的区别: 1、浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝(例:assign())
2、深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝。
在代码中浅拷贝只是赋值相等,而深拷贝会自己在堆区开辟新的内存指向赋值。
初始化列表
class Person
{
Person(int a, int b, int c) :m_A(a), m_B(b), m_(c)
{
}
int m_A;
int m_B;
int m_C;
}
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员,当其他类对象作为本类成员时,先执行类对象的构造函数,然后执行类本身的构造函数。而析构正相反,先执行类本身的析构,再执行类对象成员的析构。
静态成员
静态成员就是在成员变量和成员函数前加上关键字static。
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存(全局区)
- 类内声明,类外初始化
class Person
{
public:
static int m_A;
};
Person:: m_A = 100;
void test01()
{
Person p;
cout << p.m_A << endl;
Person p2;
p2.m_A = 200;
cout << p.m_A << endl;
}
void test02()
{
}
注:静态成员变量也是有访问权限的
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
静态成员函数与静态成员变量一样既可以通过对象访问,也可以通过类名访问 静态成员函数也是有访问权限的。
成员变量和成员函数分开存储
空对象占用空间为:1 C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置 每个空对象有独一无二的内存地址
- 非静态成员变量内存分配属于类的对象上
- 静态成员变量内存分配不属于类对象
- 非静态成员函数内存分配也不属于类对象
this指针
每一个非静态成员函数只会诞生一份函数实例,也就说多个同类型对象会共用一块代码,C++通过提供特殊的对象指针,this指针,来区分到底是那个对象在调用函数。 this指针是隐含每一个非静态成员函数内的一种指针,this指针不需要定义,直接使用。 this指针的用途:
- 当形参和成员变量同名时,可以用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
- this指针的本质是一个指针常量
const修饰成员函数
常函数:
- 成员函数后加const后我们称这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数。
class Person
{
void showPerson() const
{
this->m_B = 100;
}
int m_A;
mutable m_B;
}
void test02()
{
const Person p;
}
友元
友元的关键字为friend 介于private与public之间,卧室属于私有空间,不对公众开放,但是对闺蜜基友开放,这就是友元
class Building
{
friend void goodGay(Building *building);
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
void goodGay(Building *building)
{
cout << "好基友全局函数 正在访问;" << building -> m_SittingRoom << endl;
}
void test01()
{
Building building;
goodGay(&building);
}
本例的核心代码 friend void goodGay(Building *building);;
class Building;
class GoodGay
{
public:
GoodGay();
void visit();
Building *building;
};
class Building
{
friend class GoodGay;
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
building = new Building;
}
void GoodGay::visit()
{
cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
}
本例的核心代码friend class GoodGay;
class Building;
class GoodGay
{
public:
GoodGay();
void visit();
void visit2();
Building * building;
};
class Building
{
friend void GoodGay::visit();
public:
Building();
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
building = new Building;
}
GoodGay::visit()
{
cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}
GoodGay::visit2()
{
cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
本例的核心代码 // 告诉编译器,GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员。 friend void GoodGay::visit();
运算符重载
对已有运算符重新定义,赋予另外一种功能,以适应不同数据类型。
- 运算符重载案例
例如 长方形类有两个属性长和宽,想定义长方形对象1和长方形对象2相加为把他们各自的长和宽分别相加。
class Rectangle
{
public:
int m_width;
int m_height;
}
Rectangle r1;
r1.m_width = 10;
r1.m_height = 10;
Rectangle r2;
r2.m_width = 10;
r2.m_height = 10;
// 可以通过自己写成员函数 来实现两个对象属性相加然后返回新的对象
Rectangle RectangleAdd(Rectangle &r)
{
Rectangle temp;
temp.m_width = this->m_width + r.m_width;
temp.m_height = this->m_width + r.m_width;
return temp;
}
// 其实编译器给这种做法起了一个通用名称 operator+
Rectangle operator+(Rectangle &r)
{
Rectangle temp;
temp.m_width = this->m_width + r.m_width;
temp.m_height = this->m_width + r.m_width;
return temp;
}
当使用编译器提供的通用名称时: Rectangle r3 = r1.operator+(r2); 可以简写为: Rectangle r3 = r1 + r2; 当然也可以通过全局函数 重载
Rectangle operator+(Rectangle &r1, Rectangle &r2)
{
Rectangle temp;
temp.m_width = r1.m_width + r2.m_width;
temp.m_height = r1.m_width + r2.m_width;
return temp;
}
运算符重载也可以发生函数重载
Rectangle operator+(Rectangle &r1,int num)
{
Rectangle temp;
temp.m_width = r1.m_width + num;
temp.m_height = r1.m_width + num;
return temp;
}
Rectangle r3 = r1 + 100;
- 左移运算符重载即
<< 作用是可以输出自定义的数据类型
void operator<<(cout)
{
}
如果利用成员函数重载,则调用的时候: p.operator<<(cout) 简化为p<<cout ,而我们正常期望的效果是 cout<<p 因此我们一般不会用成员函数来重载<< 因为无法实现out在左侧。 只能利用全局函数重载左移运算符
void operator<<(cout, p)
本质 operator<<(cout,p) 简化为cout<<p
现在的问题是cout 是什么数据类型呢? 其实他是一个ostream(输入流对象)
所以我们的重载方法应该写为
ostream & operator<<(ostream &cout,Person &p)
{
cout << "m_A=" << p.m_A << "m_B=" <<p.m_B;
return cout;
}
在使用时: cout <<p 即可输出P的属性
- 递增运算符
++ 重载 功能需要包含前置递增和后置递增
class MyInteger
{
public:
MyInteger()
{
m_Num = 0;
}
private:
int m_Num;
}
void test01()
{
MyInteger myint;
cout << myint << endl;
}
ostream & operator<<(ostream & cout, MyInteger myint)
{
cout << myint.m_Num;
return cout;.
}
数据类型
数据类型取值与内存占用
typedef声明新类型
enum枚举类型
指针
指针定义与使用
指针就是一个地址编号,代表了变量存放的地址。 X86架构,32位机所有的指针都占用4个字节 而64位机所有指针都占用8个字节
int a = 10;
int * p = &a;
*p = 20;
指针与数组
数组的指针是数组第一个元素存储的地址。
int [] arr = {1,2,3,4,5};
int * p = &arr;
cout << "利用指针访问第一个元素:" << *p <<endl;
p++;
cout << "利用指针访问第二个元素:" << *p <<endl;
指针与函数
地址传递
void swap01(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
void swap02(int *p1, int *p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void swap02(int &p1, int &p2)
{
int temp = p1;
p1 = p2;
p2 = temp;
}
const修饰指针
int a = 10;
int b = 10;
int * p = &a;
const int *p1 = &a;
p = &b;
int * const p2 = &a;
*p2 = 100;
const int * const p3 = &a;
看const右侧紧跟着的是指针(type)*还是常量p,是指针就是常量指针,指向可改,指向的内容不可改;是常量就是指针常量,指向不可改,指向内容可改。
结构体指针
结构体以‘.’访问结构体的属性,结构体指针以->访问
struct Student
{
string name;
int age;
int score;
}
struct Student studenta = {"张三",18,99};
struct Student * p = &studenta;
cout << studenta.name <<endl;
cout << p->name << endl;
产生随机数
#include <ctime>
void main(){
srand((unsigned int)time(NULL));
int random = rand() % 61 + 40;
}
|