C++学习笔记
常量
c++定义常量两种方式:
1.#define宏常量
#define day 7
2.const 修饰的变量
const int day =7;
关键字
见表
标识符
变量
注:变量名、标识符都要见名知意。
sizeof(数据类型/变量)
float 4字节 有效数字范围:7位有效数字 double 8字节 有效数字范围:15~16位有效数字
break的使用:
1.出现在switch语句中;
2.出现在循环语句中;
3.出现在嵌套循环语句中;
continue作用:在循环语句中,跳过本此循环中余下尚未执行的语句,继续执行下一次循环。
goto语句:无条件跳转语句。
如果标记的名称存在,执行到goto语句时,会跳转到标记的位置。
在做值传递的时候,函数的形参发生改变,并不会影响实参。
函数份文件编写
步骤:
1.创建后缀名为.h的文件;
2.创建后缀名为.cpp的源文件;
3.在头文件中写函数的声明;
4.在源文件中写函数的定义。
指针
指针的作用:通过指针简介访问内存。
内存编号是从0开始记录的,一般用十六进制表示;
可以利用指针保存变量地址。
指针的定义:
数据类型 *指针变量名; int *p;
指针记录变量a的地址:
p = &a;
使用指针:可以使用解引用的方式来使用指针指向的内存。
指针前加*代表解引用,找到指针指向的内存中的数据。
在32位 操作系统下,指针是占4个字节空间大小,不管是什么数据类型;
在64位操作系统下,指针是占8个字节空间大小
空指针:指针变量指向内存编号为0的空间;
用途:初始化指针变量。
注意:空指针指向的内存是不可以访问的。
int *p =NULL;
野指针:指针变量指向非法内存空间;
在程序中,尽量避免使用野指针。
const修饰指针有三种情况:
1.const修饰指针 --常量指针
2.const修饰常量 --指针常量
3.const既修饰指针,又修饰常量
const int * p =&a; 常量指针 特点:指针的指向可以修改,但是指针指向的值不可以
int * const p = &a; 指针常量 特点:指针的指向不可以改,指针指向的值可以改
const int * const p = &a; 特点:指针的指向和指针指向的值都不可以改变
int arr[]; int * p = arr; arr就是数组的首地址 *p //利用指针访问第一个元素 p++; //让指针向后偏移4个字节,访问下一个元素
指针和函数
作用:利用指针做函数的参数,可以修改实参的值;
地址传递:如果是地址传递,可以修饰实参。
指针、数组、函数
结构体
结构体属于用户自定义的数据类型,允许用户存储不同类型的数据类型。
语法:
struct 结构体名 {结构体成员列表};
通过结构体穿件变量的方式有三种:
1.struct 结构体名 变量名;
2.struct 结构体名 变量名 = {成员1值;成员2值.....}
3.定义结构体时顺便创建变量
struct Student { string name; int age; int score; };
通过学生类型创建具体学生:
struct Student s1; //s1为结构体变量
给s1属性赋值,通过 . 访问结构体变量的属性:
s1.name = "张三"; s1.age = 18; s1.score = 100;
``` struct Student s2 = {“李四”,19,80}; ```
struct Student { string name; int age; int score; }s3;
结构体数组:
作用:将自定义的结构体放入到数组中方便维护。
语法:
struct 结构体名 数组名[元素个数] = { {},{},{},......{} }
结构体数组:
1.定义结构体:
struct Student { string name; int age; int score; };
2.创建结构体数组
struct Student stuArray[3] = { {"张三",18,100}, {"李四",28,99}, {"王五",38,66} };
3.给结构体数组中的元素赋值
stuArray[2].name = "赵六"; stuArray[2].age = 80; stuArray[2].score = 60;
4.遍历结构体数组
for(int i = 0; i < 3;i++) { cout << stuArray[i].name <<stuArray[i].age << stuArray[i].score << endl; }
结构体指针:
作用:利用指针访问结构体中的成员
利用操作符->可以通过结构体指针访问结构体属性。
struct Student { string name; int age; int score; };
int main() { struct student s = {"张三",18,100}; struc * p = &s; //通过指针访问结构体变量中的数据 cout << p->name << p->age << p->score << endl; }
结构体嵌套
定义老师结构体
struct student { int id; string name; int age; int score; } strucu teacher { int id; string name; int age; struct student stu; }
int main() { teacher t; t.id = 10000; t.name= "老王"; t.age = 50; t. stu.name = "小王"; t. stu.age = 20; t. stu.score = 60; }
结构体做函数参数
作用:将结构体作为参数向函数中传递
传递方式有两种:
1.值传递
2.地址传递
struct student { int id; string name; int age; int score; } //值传递 void printStudent1(struct student s) { cout << s.name << s.age << s.score << endl; } //地址传递 void printS tudent2(struct student * p) { cout << p->name << p-> age << p-score << endl; } int main() { struct student s; s.name = "张三"; s.age = 20; s.score = 80; printStudent1(s) void printStudent2(&s) }
如果不想修改主函数的数据,用值传递,反正用地址传递。
结构体中const使用场景
作用:用const来防止误操作。
struct student { string name; int age; int score; } //将函数中的形参改为指针,可以减少内存空间 void printStudent (const student * s)//加入const之后,一旦有修改的操作就会报错,可以放置误操作。 { s -> age = 150;//这句程序为修改结构体内容的操作,在加入const关键字后,这段程序将会报错。 cout << s.name << s.age << s.score << endl; } int main() { struct s = {"张三",15,70};//结构体赋值操作 printStudents(&s) cout >> s.age <<endl; }
结构体案例
案例描述:
学校正在进行毕设项目,每名老师带领5个学生,总共有三名老师,需求如下:
设计学生和老师的结构体 ,其中在老师的结构体中,有老师姓名和一个存放五名学生的数组作为成员,学生的成员有姓名、考试分数,创建数组存放三名老师,通过函数给每个老师及所带的学生赋值,最终打印出老师数据以及老师所带的学生数据。
C++核心编程
主要针对C++ 面向对象编程技术做详解,探讨C++中的核心编程和精髓。
1 内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
-
代码区:存放函数体的二进制代码,由操作系统进行管理的
-
全局区:存放全局变量和静态变量以及常量
-
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
-
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:
不同区域存放的数据,赋予不同的生命周期,灵活编程。
new操作符
c++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:
new 数据类型
利用new创建的数据,会返回该数据对应类型的指针
int * p = new int(10);
利用 delete释放
delete p;
利用new创建数组
int * arr = new int [10];
利用delete释放
delete [] arr;
引用
作用:给变量取别名
语法:
数据类型 &别名 = 原名
int a = 10; // 创建引用 int &b = a; b = 20; cout << a << endl; 输出结果为20(即a的值已经被更改了)
1.引用必须初始化 int a = 10; int &b;// 错误 int &b =a; 2.引用在初始化后,不可以改变
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参。
优点:可以简化指针修改实参。
引用做函数的返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
引用做函数的返回值
1.不要返回局部变量的引用
2.函数的调用可以作为左值
引用的本质
本质:引用的本质在c++内部实现是一个指针常量
引用一旦初始化后,就不可以改变
a = 10; //自动转换为int * const ref = &a; int& ref = a; ref = 20; //内部发现ref是引用,自动转换为*ref = 20;
常量引用
作用:常量引用主要是用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参。
int a = 10; int & ref = 10;//错误,引用必须引一块合法的内存空间 const int & ref = 10; //加上const之后,编译器将代码修改为: int temp = 10; int & ref = temp; ref = 20;//错误,加入const之后变为只读,不可以修改
作用演示:
//打印数据函数 void showValue(const int & val) { cout << "val = "<< val << endl; } //防止函数修改变量值的误操作
函数提高
函数默认参数
在c++中,函数的形参列表是可以有默认值的。
语法:
返回值类型 函数名 (参数 = 默认值) { }
int func(int a = 30,int b = 20 ,int c = 30) { return a + b + c; } //当自己传入了数据,就用自己的数据,如果没有用默认值。 //1、如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都会必须有默认值
//2、如果函数的声明有默认参数,函数实现就不能有默认参数 // 也就是说声明和实现只能有一个有参数。 int func2(int a = 10,int b =10)//函数声明 int func2(int a = 10,int b = 20) { return a + b; } int main() { func2(10,10);//函数的实现 }
函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:
返回值类型 函数名 (数据类型) { }
//占位参数 //占位参数也可以有默认参数 void func(int a,int = 10)//第二个参数就是占位参数 { cout << "this is func" << endl; } int main() { func(10,10);//在调用时还是要传入占位参数类型一致的参数。 }
函数重载
函数重载概述
作用:函数名可以相同,提高复用性
函数重载满足条件:
-
同一个作用域下
-
函数名称相同
-
函数参数类型不同,或者个数不同,或者顺序不同
注意:函数的返回值不可以作为函数重载的条件,即函数返回值、个数等不同不能作为重载条件
函数重载注意事项
-
引用作为重载条件
-
函数重载喷到函数默认参数
类和对象
c++面向对象的三大特性为:封装、继承、多态
c++认为万事万物都皆为对象,对象上有其属性和行为。
封装
封装的意义一:
-
将属性和行为作为一个整体,表现生活中的实物
-
将属性和行为加以权限和控制
封装意义:
在设计类的时候,属性和行为写在一起,表现事物:
语法:
class 类名 { 访问权限:属性 / 行为 };
类中的属性和行为 统称为成员
属性 成员属性 成员变量
行为 成员函数 成员方法
封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
-
public:公共权限 成员类内可以访问,类外可以访问
-
protected:保护权限 类内可以访问,类外不可以访问 儿子可以访问父亲中的保护内容
-
private:私有权限 类内可以访问,类外不可以访问 儿子不可以访问父亲的私有内容
protected和private的区别:继承的区别
class的默认权限是私有
struct和class区别
在c++中struct和class唯一的区别就是在于默认的访问权限不同
struct默认权限是公共
成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
对象的初始化和清理
构造函数和析构函数
构造函数主要用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
构析函数:主要作用在对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:
类名() { }
1.构造函数,没有返回值也不写void 2.函数名称与类名相同 3.构造函数可以有参数,因此可以发生重载(函数名相同) 4.程序在调用对象时候会自动调用构造,无须手动调用,而且只调用一次
析构函数语法:
~类名() { }
1.析构函数,没有返回值也不写void 2.函数名称与类名相同,在名称前加符号~ 3.析构函数不可以有参数,因此不可以发生重载 4.程序在对象销毁前自动调用析构,无需手动调用,而且只会调用一次
eg:
class Person { public: //1.1 构造函数 //没有返回值 不用写void //函数名 与类名相同 //构造函数可以有参数,可以发生重载 //创建对象的时候,构造函数会自动调用,而且只会调用一次 Person() { cout << "Person构造函数的调用" << endl; } //2.析构函数函数 进行清理的操作 //没有返回值 不写void //函数名和类名相同 在名称前加 ~ //析构函数不可以有参数,不可以发生重载 //对象在销毁前 会自动调用析构函数,而且只会调用一次 ~Person() { cout << "Person析构函数的调用" << endl; } //构造喊析构都是必须有的实现,如果我们不提供,编译器会提供一个空实现的构造和析构 };
构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
-
括号法
-
显示法
-
隐式转换法
拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
-
使用一个已经创建完毕的对象来初始化一个新对象
-
值传递的方式给函数参数传值
-
以值的方式返回局部对象
构造函数调用规则
默认情况下,C++编译器至少给一个类添加三个函数
-
默认构造函数(无参,函数体为空)
-
默认析构函数(无参,函数体为空)
-
默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下: 如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造。 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作。 深拷贝:在堆区重新申请空间,进行拷贝操作。
初识化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:
构造函数() : 属性1(值1),属性2(值2)...{}
类对象作为类成员
C++类中的成员可以是另一个类的 成员,我们称该成员为对象成员。
class A{} class B { A a; }
当其他类对象作为本类 成员,构造时候先构造对象,再构造自身
析构的顺序与构造相反:先构造本类的成员,再构造对象的,析构时,先析构对象,再析构其他类对象。
静态成员
静态成员就是在成员变量和成员函数前加关键字static,称为静态成员
静态成员分为:
静态成员变量:
-
所有对象共享同一份数据
-
在编译阶段分配内存
-
类内声明,类外初始化 静态成员变量不属于某个对象上,所有对象都共享同一份数据 因此静态成员变量有两种访问方式 1.通过对象进行访问
Person p;
2.通过类名进行访问
cout << Person::m_A << endl;
静态成员变量也有访问权限
private; static int m_B 在类外无法访问。类外访问不到私有静态成员变量
静态成员函数:
-
所有 对象共享同一个函数
-
静态成员函数只能访问静态成员变量
C++对象模型和this指针
成员变量和成员函数分开存储 在C++中,类内的成员变量和成员函数分开存储 只有在非静态成员变量才属于类的对象上
class Person { } void test01() { Person p; cout << sizeof(p) << endl; }
test01函数输出的结果为1,即空对象占用内存空间是1 C++编译器会给每个空对象也分配一个字节空间,为了区分空对象占内存的位置(每个对象占用独一无二的空间(内存地址),空对象也是)
class Person { int m_A; //非静态成员变量 属于类的对象上 static int m_B; //非静态成员变量 不属于类对象上 void func(); //非静态成员函数 不属于类对象上 }
this指针概念
已知C++中成员变量和成员函数是分开存储的 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码 那么:这一块代码如何区分哪个对象调用自己呢?
C++通过提供特殊对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象 this指针是隐含每一个非静态成员函数内的一种指针。 this指针不需要定义,直接使用。
this指针的用途:this指针的本质是指针常量,指针的指向是不可以修改的 即this指针不可以修改指针指向
-
在形参和成员变量同名时,可用this指针来区分
-
在类的非静态成员函数中返回对象本身,可使用 return *this
//解决名称冲突问题。 class Person { public: Person(int age) { //this指针指向被调用的成员函数所属的对象 this->age = age; } int age; } void test01() { Person p1(18); //p1在调用,所以this指针指向p1 }
这样写,就可以解决名称冲突问题。
class Person { public: Person(int age) { //this指针指向被调用的成员函数所属的对象 this->age = age; } void PersonAddAge(Person &p) { this->age += p.age; } int age; } //返回对象本身用 *this void test02() { Person p1(10); Person p2(10); p2.PersonAddAge(p1);//当这句代码运行完成后,返回的是void类型; //若想要实现p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); //则第一个第一个p2.PersonAddAge(p1)调用完成之后,需要返回对象本身 //则void PersonAddAge(Person &p)需要修改为: Person& PersonAddAge(Person &p) { this->age += p.age; return *this; } //p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); //这种编程思想为链式编程思想 }
const修饰成员函数
常函数:
-
成员函数后加const后称这个函数为常函数
-
常函数内不可以修改成员属性
-
成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
-
声明对象前加const称该对象为常对象
-
常对象只能调用常函数
class Person { pubulic: //this指针的本质 是指针常量 指针的指向不可以修改的 //this相当于 //const Person *const this; //在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改 void showPerson() const //加了const关键字,是常函数 { } void func() { } int m_A; mutable int m_B;//特殊变量,即使在常函数中,也可以修改这个值,加关键字mutable } void test02() { const Person p;//在对象前加const,变为常对象 p.m_A = 100;//会报错 p.m_B = 100;//不会报错,因为m_B加了mutable关键字,在常对象下也可以修改。 //常对象只能调用常函数 p.showPerson();//不报错 p.func();//报错,常对象不可以调用普通成员函数,因为普通成员函数可以修改属性 }
友元
友元的三种实现:
-
全局函数作友元
-
类作友元
-
成员函数作友元
class BUilding { //goodGay全局函数是Building的好朋友,可以访问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; cout << building->m_BedRoom << endl; } void test01() { Building building; goodGay(&building); }