c++基础入门
将当时自己自学c++做的一部分笔记记录下来,方便日后查阅。
1. c++初识
1.1 第一个c++程序
#include <iostream> //每个程序都要写这两行代码
using namespace std;
//单行注释
/*
main函数是程序的入口,
每个程序都需要有main函数
有且仅有一个
*/
int main() {
//行代码的含义在屏幕中输出hello world
cout << "hello world" << endl;
system("pause");
return 0;
}
1.2 不同类型的数据大小
1.3 常量
作用:用于记录程序中不可更改的数据类型
C++定义常量两种方式
-
#define 宏常量:
#define 常量名 常量值
- 通常在文件的上方定义,表示一个常量
-
const修饰的变量:
const 数据类型 常量名 = 常量值
- 通常在变量定义前加关键字const,修饰该变量为常量,不可修改
#include<iostream>
using namespace std;
//1.#define 宏常量
#define Day 7
int main() {
//Day = 14; 错误,Day是常量,不能修改,变量可以修改
cout << "一周共有:" << Day << "天" << endl;
//2.const修饰的变量
const int month = 12;
//month = 24; //错误,const修饰的变量也称常量
cout << "一年有" << month << "个月份" << endl;
system("pause");
return 0;
}
1.4 标识符命名规则
作用:C++规定标识符(变量、常量)命名时,有一套自己的规则
- 标识符不能是关键字
- 标识符只能由字母、数字、下划线组成
- 第一个字符必须为字母或下划线
- 标识符中字母区分大小写
建议:给标识符命名时要做到见名知意,方便自己和他人的阅读
2 数据类型
C++规定在创建一个变量或常量时,必须要指定相应的数据类型,否则无法给变量分配内存
2.1 整型
默认情况下的定义是有符号整型
short s; // prefer "short" instead of "short int"
int i;
long l; // prefer "long" instead of "long int"
long long ll; // prefer "long long" instead of "long long int"
定义无符号整数
unsigned short us;
unsigned int ui;
unsigned long ul;
unsigned long long ull;
2.2 sizeof关键字
作用:利用sizeof关键字可以统计数据类型所占内存的大小
语法:sizeof(数据类型/变量)
#include<iostream>
using namespace std;
int main() {
short num1 = 10;
int num2 = 10;
long num3 = 10;
cout << "short占用的内存空间为:" << sizeof(num2) << endl;
system("pause");
return 0;
}
2.3 实型(浮点型)
作用:用于表示小数
浮点型变量分为两种
1.单精度float
2.双精度double
默认情况下,输出一个小数,会显示6位有效数字
2.4 字符型
作用:字符型变量用于显示单个字符
语法:
char ch = 'a';
注意1:在显示字符型变量时,用单引号将字符括起来,不要用双引号
注意2:单引号内只能有一个字符,不可以是字符串
- C和C++中字符型变量只占用一个字符
- 字符型变量并不是把字符本身放到内存中去存储,而是将对应的ASCII编码放到存储单元中
#include<iostream>
using namespace std;
int main() {
short num1 = 10;
int num2 = 10;
long num3 = 10;
cout << "short占用的内存空间为:" << sizeof(num2) << endl;
//1.字符型变量创建方式
char ch = 'a';
//2.字符型变量所占用内存大小
cout << "字符型变量所占用的内存空间" << sizeof(ch) << endl; //1
//3.字符型变量常用错误
//char ch2 = "b"; //出错,不饿能使用双引号
//char ch2 = 'abcd'; //单引号内只能有一个字符
cout << (int)ch << endl; //97
system("pause");
return 0;
}
2.5 转义字符
现阶段我们常用的转义字符:\n \\ \t
2.6 字符串型
作用:用于表示一串字符
两种风格
- c风格字符串:`char 变量名[]=“字符串值”
- 示例:
char str[] = "hello world";
C++风格字符串:string 变量名 = "字符串值"
2.7 数据的输入
作用:从键盘获取数据
关键字:cin
语法: cin >> 变量
#include<iostream>
using namespace std;
int main() {
//1.整型
int a = 0;
cout << "请给整型变量a赋值" << endl;
cin >> a; // 赋值操作
cout << "整型变量a = " << a << endl;
}
2.8 一维数组的定义
//方式一
int arr[10];
arr[0] = 10;
arr[1] = 20;
...
//访问数组元素
cout << arr[0] << endl; //10
//方式二
int arr2[5] = {10,20,30,40,50}; //初始化数组
cout << arr2[0] << endl;
//利用循环输出
for(int i = 0; i < 5; i++){
cout << arr2[i] << endl;
}
//方式三
//定义数组的时候,必须有初始的长度
int arr3[] = {1,2,3,4,5,6,7}; //可以不写数组的长度
一维数组名的用途
- 可以统计整个数组在内存中的长度
- 可以获取数组在内存中的首地址
冒泡排序
//冒泡排序
/*
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个
2.对每一对相邻元素做同样的工作,执行完毕后,找到第一个最大值
3.重复以上步骤,每次比较次数-1,直到不需要比较
排序总轮数 = 元素个数 - 1;
每轮对比的次数 = 元素个数 - 排序轮数 - 1;
*/
int arr[] = { 4,2,8,0,5,7,1,3,9 };
cout << "排序前:" << endl;
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
//外层循环表示排序的轮数,元素个数 - 1
for (int i = 0; i < 9 - 1; i++) {
//内层循环对比,次数 = 元素个数 - 当前轮数 - 1
for (int j = 0; j < 9 - i - 1; j++) {
//如果第一个数字比第二个数字大,就交换他们
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
cout << "排序后的结果" << endl;
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
2.9 二维数组
//方式一,自己进行数据的赋值
int arr[2][3];
arr[0][0] = 1;
arr[0][1] = 2;
arr[1][2] = 3;
...
//方式二,很直观 (推荐)
int arr2[2][3]
{
{1,2,3},
{4,5,6}
}
//方式三:程序可以划分出来
int arr3[2][3] = {1,2,3,4,5,6};
//方式四:可以省略行数,不能省略列数
int arr4[][3] = {1,2,3,4,5,6};
二维数组的数组名称
- 查看二维数组所占内存空间大小
- 数组名作为起始地址
3 函数
3.1 函数的分文件编写
函数的头文件的编写(正规)
解释:如果没用定义过COMPLEX,那就定义它,最后有个结束,头文件是一种声明
#include<iostream>
using namespace std;
#include "swap.h" //将头文件加载进来
//函数的分文件编写
//实现两个数字进行交换的函数
函数的声明
//void swap(int a, int b);
//函数的定义
//void swap(int a, int b) {
// int temp = a;
// a = b;
// b = temp;
// cout << "交换后的数:" << endl;
// cout << "a = " << a << endl;
// cout << "b = " << b << endl;
//}
/*
函数的分文件的编写
1.创建.h后缀名的头文件
2.创建.cpp后缀名的源文件
3.在头文件中写函数的声明
4.在源文件中写函数的定义
*/
int main() {
//二维数组名称用途
//int arr[2][3] =
//{
// {1,2,3},
// {4,5,6}
//};
int a = 10;
int b = 20;
swap(a, b);
//理解值传递机制,实参中的值并未交换
cout << "实参中的数:" << endl;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
函数的头文件
函数的源文件
要将自己的头文件在前面加载进来
4.指针
指针在32位操作系统下占用4个字节,在64位操作系统下占用8个字节
#include<iostream>
using namespace std;
int main() {
//1、定义指针,指针也是一种数据类型
int a = 10;
//2.指针的定义语法:数据类型 *指针变量
int * p;
//3.让指针记录变量a的地址
p = &a;
cout << "a的地址为:" << &a << endl;
cout << "p的地址为:" << p << endl;
//使用指针
//可以通过解引用的方式来找到指针指向的内存
//指针前加*代表解引用,找到指针指向的内存中的数
*p = 1000;
cout << "a的值:" << a << endl;
cout << "p的值:" << *p << endl;
system("pause");
return 0;
}
4.1 空指针
//空指针
//1.空指针用于给指针变量进行初始化
int* p = NULL; //地址编号为0
//2.空指针是不可以进行访问的
//*p = 100; 会产生异常,因为0-255内存编号是系统占用的,不可以访问
4.1.1 指针的各种形式
指针就是地址,地址就是指针
地址就是内存单元的编号,地址是从零开始的非负整数
指针变量是存放地址的变量
指针和指针变量是两个不同的概念
指针的重要性
指针:
表示一些复杂的数据结构
快速的传递数据
是函数返回一个以上的值
能直接的访问硬件
能够方便的处理字符串
是理解面向对象语言中引用的基础
4.2 const修饰指针
- const修饰指针 -----常量指针 ,指针指向常量
- const修饰常量 -----指针常量
- const既修饰指针,又修饰常量
//常量指针
const int * p = &a; //常量指针 特点:指针的指向可以修改,但是指针指向的值不可以修改
*p = 20; //错误,不能修改值
p = &b; //正确,指针的指向被修改
//*p的值为const,20,不能被修改 但指针p的指向可以改变,可以指向另一块内存空间
//指针常量
int * const p = &a; //指针常量(看const后面跟的是谁),指针的指向不可以改,指针指向的值可以改
*p = 20; //正确,指向的值可以改
p = &b; //错误,指针的指向不可以修改
//既修饰指针,又修饰常量
const int * const p = &a;
*p = 20; //错误
p = &b; //错误
4.3 指针和函数
#include<iostream>
using namespace std;
//参数一 :数组的首地址,参数二:数组长度
void bubbleSort(int * arr,int len) {
for (int i = 0; i < len; i++) {
for (int j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
void printArray(int* arr, int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
int main() {
//1.创建数组
int arr[] = { 4,3,6,9,1,2,10,8,7,5 };
int len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len);
printArray(arr, len);
}
4.4 结构体
4.4.1 定义结构体变量
#include<iostream>
using namespace std;
#include<string>
struct Student { //此关键字不可以省略
int age;
string name;
int score;
} s3;
//通过学生类型创建具体的学生
int main() {
//第一种方式
struct Student s1; //相当于创建对象
s1.age = 12;
s1.name = "张三";
s1.score = 100;
cout << "姓名:" << s1.name << " 年龄:" << s1.age << " 分数:" << s1.score << endl;
//第二种方式
struct Student s2 = { 23, "李四", 90 };
cout << "姓名:" << s2.name << " 年龄:" << s2.age << " 分数:" << s2.score << endl;
//第三种方式:在定义结构体时顺便创建结构体变量
s3.age = 33;
s3.name = "小红";
s3.score = 89;
cout << "姓名:" << s3.name << " 年龄:" << s3.age << " 分数:" << s3.score << endl;
system("pause");
return 0;
}
4.4.2 定义结构体数组
//定义结构体数组
struct Teacher {
int age;
string name;
int score;
};
//创建结构体数组(相当于创建对象)
Teacher teaArray[3] = {
{ 28, "Lily", 99},
{ 78, "Tom",66},
{45,"David",89}
};
//给结构体数组中的元素赋值
teaArray[2].age = 55;
teaArray[2].name = "Lisa";
teaArray[2].score = 78;
//遍历结构体数组
for (int i = 0; i < 3; i++) {
cout << "姓名 : " << teaArray[i].name
<< " 年龄: " << teaArray[i].age
<< " 分数:" << teaArray[i].score << endl;
}
4.4.3 定义结构体指针
#include<iostream>
using namespace std;
#include<string>
//定义学生的结构体
struct student
{
string name;
int age;
int score;
};
int main() {
//1.创建学生结构体变量
struct student s = { "张三", 23,100 };
//2.通过指针指向结构体变量
student* p = &s; //将s的地址赋值给指针p
//通过指针访问结构体变量中的数据
cout << "姓名: " << p->name << " 年龄: " << p->age << " 分数: " << p->score << endl;
system("pause");
return 0;
}
4.4.4 结构体做函数参数
#include<iostream>
using namespace std;
#include<string>
//定义学生的结构体
struct student
{
string name;
int age;
int score;
};
//void printStudent1(struct student s) { //值传递
// cout << "姓名:" << s1.name << " 年龄:" << s1.age << " 分数:" << s1.score << endl;
//
//}
//地址传递
void printStudent2(struct student *p)
{
cout << "姓名: " << p->name << " 年龄: " << p->age << " 分数: " << p->score << endl;
}
int main() {
//1.创建学生结构体变量
struct student s = { "张三", 23,100 };
//结构体做函数参数
//printStudent1(s);
printStudent2(&s);
system("pause");
return 0;
}
5. 程序的内存模型
1.1 程序运行前
分为全局区、代码区、栈区、堆区
局部变量定义在main函数外面
全局区存放:全局变量、静态变量、常量
总结:
- C++中在程序运行前分为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放局部变量、静态变量、常量
- 常量区中存放const修饰的全局常量和字符串常量
1.2程序运行后
栈区:
由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区:
程序员管理程序的生死
#include<iostream>
using namespace std;
int* func()
{
//利用new关键字 可以将数据开辟到堆区
//指针 本质也是局部变量,放在栈上,指针保存的数据放在堆区
int* p = new int(10); //创建好堆区之后,并不是将数据给返回,返回的是地址,所以用指针接受
return p;
}
int main() {
//在堆区开辟数据
int* p = func();
cout << *p << endl; //可以重复打印
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
1.3 new操作符
C++ 中利用new操作符在堆区中开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型指针
6.引用
引用为对象起了另外一个名字,引用类型引用另外一种类型。通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名。
//语法:数据类型 &别名 = 原名
int ival = 1024;
int &refVal = ival; //refVal指向ival(是ival的另一个名字)
int &refVal2; //报错:引用必须被初始化
注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始化对象一直绑定在一起。因而无法令引用重新绑定到另外一个对象,因此引用必须初始化
int main()
{
int rats = 101;
int* pt = &rats;
int& rodents = *pt; //*pt是解引用,得到的是101
int bunnies = 50;
pt = &bunnies;
cout << "rats address:" << &rats << endl;
cout << "rodents address:" << &rodents << endl;
cout << "bunnies address:" << &bunnies << endl;
//pt指针改变,并不会改变rodents的引用,指针可以改变,引用一旦初始化,不可以改变
cout << "*pt address:" << pt << endl;
system("pause");
return 0;
}
6.2 引用做函数参数
**作用:**函数传参时,可以利用引用的技术让形参修饰实参
**优点:**可以简化指针修改实参
//值传递
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
//地址传递
void swap02(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
//引用传递
void swap03(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
swap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "按地址传递" << endl;
swap02(&a, &b); //形参修饰实参
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "按引用传递" << endl;
swap03(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
6.3 引用做函数的返回值
- 不要返回局部变量的引用
- 函数的调用可以作为左值
//1.不要返回局部变量的引用
int& test01()
{
int a = 10; //局部变量存放在四区中的栈区
return a;
}
//2.函数的调用可以作为左值
int& test02()
{
static int a = 10; //静态变量,存放在全局区,
//全局区上的数据在程序结束后系统释放
return a;
}
int main()
{
/*int &a = test01();
cout << a << endl; //第一次输出正确,第二次输出乱码
cout << a << endl;*/
int& res = test02();
cout << res << endl;
cout << res << endl;
cout << res << endl;
test02() = 1000; //如果函数的返回值是引用,这个函数对的调用可以作为左值
cout << res << endl;
system("pause");
return 0;
}
6.4 引用的本质
本质:引用的本质在c++内部实现是一个指针常量(指针的指向不可以修改,指针的值可以修改)
//发现是引用,转换成int* const ref = &a;
void func(int& a)
{
ref = 100; //ref是引用,转换成*ref = 100;
}
int main()
{
int a = 10;
//自动转换成 int* const ref = 10; 指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref = a;
ref = 20; //内部发现ref是引用,自动帮我们转换成:*ref = 20;
cout << "a:" << a << endl;
cout << "ref" << ref << endl;
}
6.5 常量引用
void showValue(const int& val) //传进来的值不能被修改
{
//val = 1000;
cout << "val = " << val << endl;
}
int main()
{
//常量引用
//使用场景:用来修饰形参,防止误操作
//加上const之后,编译器将代码修改 int temp = 10; const int & ref = temp;
const int& ref = 10; //引用必须引一块合法的内存空间
//ref = 20; //加入const变成只读,不可以修改
int a = 10;
showValue(a);
cout << "a = " << a << endl; //不加const,会被修改
system("pause");
return 0;
}
7 函数高级应用
7.1 函数默认参数
- 如果我们自己传入数据,就用自己的数据,如果没有,就用默认值
- 如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认参数
- 如果函数声明有默认参数,函数实现就不能有默认参数。声明和实现只能有一个有默认参数
7.2 占位参数
语法:返回值类型 函数名 (数据类型){ }
void func(int a,int)
{
cout << "this is func" << endl;
}
int main()
{
func(10,10);
}
7.3 函数重载
作用:函数名可以相同,提高复用性
函数重载满足条件
- 同一个作用域
- 函数名称相同
- 函数参数类型不同或者个数不同或者顺序不同
- 函数的返回值不可以作为函数重载的条件
重载的注意事项:
7.4 struct和class区别
在c++中struct和class的唯一区别是默认的访问权限不同
struct的默认访问权限为公共
class的默认访问权限为私有
7.5 指针和const
8 类和对象
8.1 构造函数和析构函数
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次
- 构造函数要这样写:
class Complex
{
public:
//这种构造器的写法非常的大气,
Complex(double r, double i)
:re(r), im(i) //代表的是初始化,
{ } //大括号是赋值
private:
double re, im;
析构函数语法:~类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同。在名称前面加~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次
- 不带指针的类大部分不用写析构函数
#include<iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person构造函数的调用" << endl;
}
~Person()
{
cout << "Person析构函数的调用" << endl;
}
};
void test01()
{
Person p2; //局部变量,在栈上的数据,调用完这个函数会被释放
}
int main()
{
//Person p1; //写到main函数中,对象不会被释放,在main函数执行完才会被释放
test01();
system("pause");
return 0;
}
构造函数的分类:按照参数分为有参构造和无参构造
按照类型分类分为:普通构造和拷贝构造
- 何时调用拷贝构造函数:新建一个对象并将其初始化为同类现有对象时,拷贝构造函数将被调用。
#include<iostream>
using namespace std;
class Person
{
public:
//构造函数
Person()
{
cout << "Person构造函数的调用" << endl;
}
Person(int a) //分类分为有参构造和无参构造
{
age = a;
cout << "Person的有参构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "Person析构函数的调用" << endl;
}
//拷贝构造函数
Person( const Person &p)
{
//将传入的人身上所有的属性,拷贝到我身上,
//调用的是构造函数中的属性和函数
age = p.age;
cout << "Person拷贝函数的调用" << endl;
}
int age;
};
void test01()
{
//1、括号法
Person p1; //默认构造函数的调用
Person p2(10); //有参构造函数
Person p3(p2); //拷贝构造函数
cout << "p2的年龄:" << p2.age << endl;
cout << "p3的年龄:" << p3.age << endl;
//2、显示法
}
int main()
{
test01();
system("pause");
return 0;
}
8.2拷贝构造函数调用时机
调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
8.3 构造函数的调用规则
8.4 深拷贝与浅拷贝
浅拷贝:只有一个堆区,会导致堆区内存重复释放
深拷贝:两个对象各自指向一个堆空间,自己实现拷贝构造函数,解决浅拷贝带来的问题
class Person
{
public:
Person()
{
cout << "Person的无参构造器" << endl;
}
Person(int a,int height)
{
age = a;
m_height = new int (height); //开辟到堆区
cout << "Person的有参构造器" << endl;
}
Person(const Person& p)
{
cout << "Person的拷贝构造器" << endl;
age = p.age;
//m_height = p.m_height; 编译器默认实现这句代码
//我们自己进行深拷贝
m_height = new int(*p.m_height);
}
~Person()
{
//析构代码,将堆区开辟的数据做出释放操作
cout << "Person的析构构造器" << endl;
if (m_height != NULL)
{
delete m_height;
m_height = NULL;
}
}
int age;
int* m_height; //堆区的变量需要用指针来接收
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄:" << p1.age << " 身高为:" << *p1.m_height << endl;
Person p2(p1); //创建拷贝构造函数对象
Person p2 = p1; //与上一行代码完全相同
//我们没有创建拷贝函数,但是编译器会自动给我们创建并调用
cout << "p2的年龄:" << p2.age << " 身高为:" << *p2.m_height << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
总结:如果属性有在堆区开辟的,一定要自己提供拷贝函数,防止浅拷贝带来的问题
三大特殊构造函数:拷贝构造,拷贝赋值,析构函数
理解这三大构造函数
class with pointer members 必须有 copy stor(拷贝构造) 和 copy op=(拷贝赋值)
使用默认拷贝构造函数会造成浅拷贝(编译器只是把指针拷贝过来),会出现内存泄露,我们自己写深拷贝来解决,自己再new一个内存空间,存放在堆区。
拷贝赋值的经典写法(下面的1,2,3步骤)
自我检测赋值是很好的习惯,不止是为了效率,也是为了安全性
切记:new之后要delete,而且 array new 一定要搭配 array delete
8.5 初始化列表
作用:
c++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)....{}
class Person{
//初始化列表初始化属性
Person(int a,int b,int c):m_A(a), m_B(b), m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
8.6 类对象作为类成员
class Phone
{
public:
//类函数
Phone(string pName)
{
m_PName = pName;
}
~Phone()
{
cout << "Phone析构函数被调用" << endl;
}
//类属性,手机的品牌名称
string m_PName;
};
class Person
{
public:
//初始化列表
Person(string name, string pName) : m_Name(name), m_Phone(pName)
{
cout << "Person构造函数应用" << endl;
}
~Person()
{
cout << "Person析构函数被调用" << endl;
}
string m_Name;
Phone m_Phone; //类对象作为类成员
};
//当其他类的对象,作为本类的成员,构造时候先构造其他类的对象,再构造自身类的对象
//析构的顺序与构造的顺序相反
void test01()
{
Person p("张三", "苹果MAX");
cout << p.m_Name << "拿着" << p.m_Phone.m_PName << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
//运行结果
Person构造函数应用
张三拿着苹果MAX
Person析构函数被调用
Phone析构函数被调用
请按任意键继续. . .
8.7 静态成员
#include <iostream>
using namespace std;
#include<string>
class Person
{
public:
static int m_A;
};
// 类内声明,类外初始化
int 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.通过对象进行访问,非静态只能通过创建对象进行访问
Person p;
cout << p.m_A << endl;
//2.通过类名进行访问
cout << Person::m_A << endl;
}
int main()
{
//test01(); //100 200
test02();
system("pause");
return 0;
}
静态成员变量也是有访问权限的,私有的在类外访问不到
8.7.2静态成员函数
链接性为内部的静态变量可以在同一个文件的多个函数之间共享数据
使用外部变量在多文件程序的不同部分之间共享数据
链接性为内部的变量会覆盖全局变量
8.8 成员变量和成员函数
空对象占用内存空间为:1
C++编译器会给每一个空对象分配一个字节空间,是为了区分空对象占内存的位置
每个空对象也应该有一个独一无二的内存地址
8.8.1 this指针概念
#include<iostream>
using namespace std;
class Person
{
public:
//有参构造函数
Person(int age)
{
this->age = age; //哪个对象调用了此构造器,this就指向哪里
}
//再创建一个成员函数
//void PersonAddAge(Person& p) //用引用接收
//{
// this->age += p.age; //现在是p2调用了此函数,所以this指向P2
//}
Person& PersonAddAge(Person& p) //用引用接收
{
this->age += p.age; //现在是p2调用了此函数,所以this指向P2
return *this; //指向了p2的本体,用引用的方式返回
}
int age;
}; //类最后一定要加";"
//创建函数
void test01()
{
//创建类的对象,给类中的构造函数初始化
Person p1(10);
cout << "年龄为:" << p1.age << endl;
}
void test02()
{
Person p1(18);
Person p2(10);
//p2.PersonAddAge(p1);
//cout << "p2的年龄为:" << p2.age << endl; //输出28
//链式编程思想
//每次调用一次,返回值都是p2,如果前面返回的是值,就不会连续相加
//因为调用值的时候,会调用拷贝函数,将值复制一份,是p2',是新的对象
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
//此刻p2调用了PersonAddAge(),所以this指向了p2
cout << "p2的年龄为:" << p2.age << endl;//10+18+18+18=64
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
8.8.2 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include<iostream>
using namespace std;
class Person
{
public:
void showPerson()
{
cout << "this is a showPerson" << endl;
}
void showPersonAge()
{
if (this == NULL) //提高代码的健壮性,如果传进来是空指针,就直接终止程序了
{
return;
}
cout << "age = " << age << endl;
}
int age;
};
void test01()
{
Person* p = NULL;
p->showPerson();
p->showPersonAge(); //会出错,因为指针指向的是空指针,没有确定的对象,
//不能调用确定对象的属性。
}
int main()
{
system("pause");
return 0;
}
8.8.3 const修饰成员函数
8.8.4 friend(友元)
class Complex
{
public:
//这种构造器的写法非常的大气,
Complex(double r, double i)
:re(r), im(i) //代表的是初始化,
{ } //大括号是赋值
private:
double re, im; //实部和虚部是私有的,外界不能直接拿到
//friend是友元,
friend complex& _doapl(complex*,const complex&);
};
inline complex&
_doapl(complex* this,const complex& r)
{
this->re += r.re;
this->im += r.im;
return *this; //自由取得friend的private成员,打破了封装了大门
}
8.8.5 运算符的重载
作用:实现两个自定义数据类型相加的运算
对于内置的数据类型,编译器知道如何运算,但是两个其他类型(自定义)的相加,编译器不知道该怎么做
8.9 继承
8.9.1 继承的方式
继承的语法:class子类:继承方式 父类
class Java : public Base{}
继承的方式有三种:
- 公共继承
- 保护继承
- 私有继承
8.9.2 继承中的对象模型
利用开发人员命令提示工具查看对象模型
跳转盘符 F:
跳转文件路径 cd 具体路径下
查看命名:cl /dl reportSingleClassLayout类名 文件名
继承:构造和析构的顺序
静态同名成员属性,函数调用
#include<iostream>
using namespace std;
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base static func()" << endl;
}
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "son static func()" << endl;
}
};
int Son::m_A = 200;//静态属性,类中定义,类外声明
void test01()
{
Son s; //创建Son类的对象,通过对象访问静态属性
cout << "Son中的m_A:" << s.m_A << endl;
cout << "Base中的m_A:" << s.Base::m_A << endl;
//方式二:
cout << "Son中的m_A:" << Son::m_A << endl;
//第一个::代表通过类名的方式访问,第二个::代表访问父类作用域下
cout << "Base中的m_A:" << Son::Base::m_A << endl;
}
void test02()
{
//测试同名的静态方法
Son s;
s.func();
s.Base::func();
/*cout << "Son中的静态方法:" << s.func << endl;错误了
cout << "Son中的静态方法:" << s.Base::func << endl;*/
Son::func();
Son::Base::func();
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
8.10 多态
C++提高编程
1.模板
1.1 类模板
1.2 函数模板
2.STL初识
2.1 STL基本概念
2.2 STL六大组件
2.3 STL容器、算法、迭代器
2.3.1 vector存放内置数据类型
容器:vector
算法:for_each
迭代器:vector<int>:: iterator
** 迭代器的三种遍历方式**
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
void myPrint(int val)
{
cout << val << endl;
}
//vector容器存放内置数据类型
void test01()
{
//初始化迭代器
vector<int> v;
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
v.push_back(50);
//每个容器都有自己的迭代器,迭代器是用来遍历容器中的元素
//v.begin()返回迭代器,这个迭代器指向容器中的第一个元素
//v.end()返回迭代器,这个迭代器指向容器元素的最后一个元素的下一个位置
//vector<int> ::iterator拿到vector<int>这种容器的迭代器类型
//第一种遍历方式
vector<int> ::iterator it_begin = v.begin();
vector<int> ::iterator it_end = v.end();
while (it_begin != it_end)
{
cout << *it_begin << endl;
it_begin++;
}
//第二种遍历方式
for (vector<int> ::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << endl;
}
//第三种遍历方式 利用STL提供的遍历算法
for_each(v.begin(), v.end(), myPrint);
}
int main()
{
test01();
system("pause");
return 0;
}
2.3.2 vector容器中存放自定义的数据类型
#include<iostream>
using namespace std;
#include<vector>
#include<string>
class Person
{
public:
Person(string name,int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
void test01()
{
vector<Person> v;
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
Person p4("ddd", 40);
Person p5("eee", 50);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
//遍历
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
{
cout << "姓名" << (*it).m_Name << "年龄" << (*it).m_Age << endl;
cout << "姓名:" << it->m_Age << "年龄:" << it->m_Age << endl;
}
}
//放对象指针
void test02()
{
vector<Person*> v;
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
Person p4("ddd", 40);
Person p5("eee", 50);
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
v.push_back(&p4);
v.push_back(&p5);
for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++)
{
cout << "姓名:" << (*it) ->m_Age << "年龄:" << (*it)->m_Age << endl;
}
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
2.3.3 Vector容器嵌套容器
#include<iostream>
using namespace std;
#include<vector>
void test01()
{
vector<vector<int>> v;
//创建小容器
vector<int> v1;
vector<int> v2;
vector<int> v3;
vector<int> v4;
for (int i = 0; i < 4; i++)
{
v1.push_back(i + 1);
v2.push_back(i + 2);
v3.push_back(i + 3);
v4.push_back(i + 4);
}
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
v.push_back(v4);
//遍历嵌套容器
for (vector < vector<int>> ::iterator it = v.begin(); it != v.end(); it++)
{
//it是一个指针,*it是解引用,代表的是尖括号里面的东西
for (vector<int> ::iterator vit = (*it).begin(); vit != (*it).end(); vit++)
{
cout << *vit << " ";
}
cout << endl;
}
}
int main()
{
test01();
//test02();
system("pause");
return 0;
}
2.4 string基本概念
2.4.1 string赋值操作
#include<iostream>
using namespace std;
#include<string>
void test01()
{
string str1;
str1 = "helllo world";
cout << "str1 = " << str1 <<endl;
string str2;
str2 = str1;
cout << "str2 = " << str2 << endl;
string str3;
str3 = 'a';
cout << "str3 = " << str3 << endl;
string str4;
str4.assign("hello C++"); //直接赋值字符串
cout << "str4 = " << str4 << endl;
string str5;
str5.assign("hello C++", 5); //将字符串的前5个字符取出来
cout << "str5 = " << str5 << endl;
string str6;
str6.assign(str5);
cout << "str6 = " << str6 << endl;
string str7;
str7.assign(10, 'w');
cout << "str7 = " << str7 << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
2.4.2string字符串的拼接
#include<iostream>
using namespace std;
#include<string>
void test02()
{
string str1;
str1 = "I";
str1 += " love playing game";
cout << str1 << endl;
str1 += ':';
cout << str1 << endl;
string str2;
str2 = " LOL DNF";
str1.append(str2); //在str1后面追加str2
cout << str1 << endl;
string str3;
str3 = "I love:";
str3.append(str2, 4, 7);
cout << str3 << endl;
}
int main()
{
test02();
system("pause");
return 0;
}
2.4.3 string的查找与替换
void test03()
{
string str1 = "abcdefde";
int pos = str1.find("de"); //找到返回起始下标,未找到返回-1;
cout << pos << endl;
int pos2 = str1.rfind("de"); //rfind从右往坐查找,与find相反,但索引下标是从左往右
cout << pos2 << endl;
}
void test04()
{
string str1 = "abcdefg";
str1.replace(1, 3, "1111");
//replace在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串
cout << str1 << endl;
}
2.4.4 string字符串的比较
void test05()
{
string str = "hello";
//通过[]访问单个字符
for (int i = 0; i < str.size(); i++)
{
cout << str[i] << " ";
}
cout << "另一种方式" << endl;
//通过at方式访问单个字符
for (int i = 0; i < str.size(); i++)
{
cout << str.at(i) << " ";
}
cout << endl;
//修改单个字符
str[0] = 'x';
cout << str << endl;
str.at(2) = 'x';
cout << str << endl;
}
2.4.5 string插入和删除
2.5 vector容器
void printVector(vector<int>& v)
{
for (vector<int> ::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " "; //it是一个指针,存放的是地址,需要进行解引用
}
cout << endl;
}
void test01()
{
//第一种构造方式,,默认无参构造器
vector<int>v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
printVector(v1);
//第二种方式,拷贝构造
vector<int> v2(v1);
printVector(v2);
}
int main()
{
test01();
system("pause");
return 0;
};
2.5.1 vector赋值操作
//赋值
vector<int> v2;
v2 = v1;
printVector(v2);
//assign
vector<int> v3;
v3.assign(v1.begin(), v1.end());
printVector(v3);
vector<int> v4;
v4.assign(10, 100); //打印10个100;
printVector(v4);
2.5.2 vector容量和大小
void test02()
{
vector<int>v1;
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
printVector(v1);
if (v1.empty())
{
cout << "v1为空" << endl;
}
else
{
cout << "v1不为空" << endl;
cout << "v1的容量是:" << v1.capacity() << endl; //13
cout << "v1的大小是:" << v1.size() << endl; //10
}
//重新指定大小
v1.resize(15); //利用重载的版本,可以指定默认填充值,参数2
//如果重新指定的比原来的长了,默认用0填充新的位置
printVector(v1);
}
2.5.3 vector插入和删除
插入操作第一个参数是提供一个迭代器
/*
vector容器有动态扩充机制,如果空间不够,会再开辟一个大的空间,将原来空间的数据拷贝到新的空间,再将原来的空间舍弃掉。如果数据量很大,可以先设置一个预留空间,这样就不用重新开辟新的空间了。
*/
void test03()
{
vector<int> v;
int num = 0;
int* p = NULL;
v.reserve(100000);
for (int i = 0; i < 100000; i++)
{
v.push_back(i);
if (p != &v[0]) {
p = &v[0];
num++;
}
}
cout << num << endl;
}
2.5 deque容器
2.5.1 deque容器基本概念
void printDeque(const deque<int>& d) //这里加了const限定,下面的迭代器也要改成const迭代器
{
//打印输出与vector容器非常相似,所以扩展一些功能
for (deque<int> ::const_iterator it = d.begin(); it != d.end(); it++)
{
//*it = 100; //对容器做了修改,如果想要只读,需要加const
cout << *it << " ";
}
cout << endl;
}
void test04()
{
deque<int> d1;
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);
deque<int> d2(d1.begin(), d1.end());//拷贝构造,将d1的数据赋值给d2
printDeque(d2);
deque<int>d3(10, 100); //10个100
printDeque(d3);
deque<int> d4(d3);
printDeque(d4);//拷贝构造
}
deque容器和vector容器构造方式基本一样。
2.5.2 deque容器赋值操作
void test05()
{
deque<int> d1;
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);
//赋值操作
deque<int> d2;
d2 = d1;
printDeque(d2);
deque<int> d3;
d3.assign(d1.begin(), d1.end());
printDeque(d3);
deque<int> d4;
d4.assign(10, 100);
printDeque(d4);
}
2.5.3 deque大小操作
- deque容器没用容量限制
- 判断是否为空—empty
- 返回元素的个数—size
- 重新指定个数—resize
2.5.4 deque插入和删除
void test06()
{
deque<int> d1;
//尾插
d1.push_back(10);
d1.push_back(20);
//头插
d1.push_front(1);
d1.push_front(2);
printDeque(d1);
//头删
d1.pop_front();
//尾删
d1.pop_back();
printDeque(d1);
//在某个位置插入删除操作
d1.insert(d1.begin(), 10);
printDeque(d1);
deque<int>d2;
d2.push_back(100);
d2.push_back(200);
d2.push_back(300);
d1.insert(d1.end(), d2.begin(), d2.end());
printDeque(d1);
deque<int>::iterator it = d1.begin();
it++;
d1.erase(it);
printDeque(d1);
}
2.5.5 deque数据存取
void test07()
{
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_back(30);
d.push_front(100);
d.push_front(200);
d.push_front(300);
//访问元素,通过[]
for (int i = 0; i < d.size(); i++)
{
cout << d[i] << " ";
}
cout << endl;
//通过at()访问
for (int i = 0; i < d.size(); i++)
{
cout << d.at(i) << " ";
}
cout << endl;
cout << "第一个元素:" << d.front() << endl;
cout << "最后一个元素:" << d.back() << endl;
}
2.5.5 deque排序
sort()方法
sort(d.begin(),d.end());
//升序排列
随机数生成方法
number = rand() % (MAX_VALUE - MIN_VALUE + 1) + MIN_VALUE;
2.6 stack容器
读,需要加const
cout << *it << " ";
}
cout << endl;
}
void test04()
{
deque d1;
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);
deque d2(d1.begin(), d1.end());//拷贝构造,将d1的数据赋值给d2
printDeque(d2);
dequed3(10, 100); //10个100
printDeque(d3);
deque d4(d3);
printDeque(d4);//拷贝构造
}
deque容器和vector容器构造方式基本一样。
#### 2.5.2 deque容器赋值操作
[外链图片转存中...(img-Mz9iuU8M-1700207082469)]
```c++
void test05()
{
deque<int> d1;
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);
//赋值操作
deque<int> d2;
d2 = d1;
printDeque(d2);
deque<int> d3;
d3.assign(d1.begin(), d1.end());
printDeque(d3);
deque<int> d4;
d4.assign(10, 100);
printDeque(d4);
}
2.5.3 deque大小操作
[外链图片转存中…(img-yaju2lvM-1700207082470)]
- deque容器没用容量限制
- 判断是否为空—empty
- 返回元素的个数—size
- 重新指定个数—resize
2.5.4 deque插入和删除
[外链图片转存中…(img-8tBiaofa-1700207082470)]
void test06()
{
deque<int> d1;
//尾插
d1.push_back(10);
d1.push_back(20);
//头插
d1.push_front(1);
d1.push_front(2);
printDeque(d1);
//头删
d1.pop_front();
//尾删
d1.pop_back();
printDeque(d1);
//在某个位置插入删除操作
d1.insert(d1.begin(), 10);
printDeque(d1);
deque<int>d2;
d2.push_back(100);
d2.push_back(200);
d2.push_back(300);
d1.insert(d1.end(), d2.begin(), d2.end());
printDeque(d1);
deque<int>::iterator it = d1.begin();
it++;
d1.erase(it);
printDeque(d1);
}
2.5.5 deque数据存取
[外链图片转存中…(img-2W1gI6nm-1700207082470)]
void test07()
{
deque<int> d;
d.push_back(10);
d.push_back(20);
d.push_back(30);
d.push_front(100);
d.push_front(200);
d.push_front(300);
//访问元素,通过[]
for (int i = 0; i < d.size(); i++)
{
cout << d[i] << " ";
}
cout << endl;
//通过at()访问
for (int i = 0; i < d.size(); i++)
{
cout << d.at(i) << " ";
}
cout << endl;
cout << "第一个元素:" << d.front() << endl;
cout << "最后一个元素:" << d.back() << endl;
}
2.5.5 deque排序
sort()方法
sort(d.begin(),d.end());
//升序排列
随机数生成方法
number = rand() % (MAX_VALUE - MIN_VALUE + 1) + MIN_VALUE;