C++学习网:www.studycpp.cn/
初级篇
基础语法
变量与常量
变量初始化
C++中有6种基本的变量初始化方法:
int a; // 默认初始化,变量的值不确定
int b = 5; // 拷贝初始化
int c( 6 ); // 直接初始化
// 列表初始化 (C++11引进)
int d { 7 }; // 直接列表初始化
int e = { 8 }; // 拷贝列表初始化
//值初始化将变量初始化为零(或空,如果这更适合给定类型)。发生归零的这种情况,称为零初始化。
int f {}; // 值列表初始化
常量
- C++支持两种不同类型的常量:
- 命名常量(Named constants)是与标识符关联的常量值。这些有时也被称为符号常量(symbolic constants),或者只称为常量(constants)。
- 字面值常量(Literal constants)是与标识符无关的常量值。
- 在C++中定义命名常量有三种方法:
- 将变量设置为不可更改,即为常变量(Constant variables)。
- 定义常变量时必须对其进行初始化,之后不能通过赋值更改该值
- 通过值传递时不要使用const。
- 按值返回时不要使用const。
- 具有替换文本的类对象宏。
- 定义常量时,不推荐使用类对象宏。
- 枚举常数。
- 将变量设置为不可更改,即为常变量(Constant variables)。
- as-if规则: 编译器可以随意修改程序,以产生更优化的代码,只要这些修改不影响程序的“可观察行为”。
常量表达式(constant expression)是编译器可以在编译时计算的表达式。要成为常量表达式,表达式中的所有值都必须在编译时已知(并且调用的所有运算符和函数都必须支持编译时求值)。表达式 “3 + 4” 是一个常量表达式。因此,当编译该程序时,编译器可以将其替换为结果值7。编译时常量是其值为常量表达式的常量。字面值(例如 1、2.3 和“Hello,world!”)是编译时常量的一种类型。常变量可以是也可以不是编译时常量(取决于它们的初始化方式)。- 如果常变量的初始值是非常量表达式,则是
运行时常量。运行时常量是在运行时之前无法确定其初始化值的常量。 - 当使用const时,变量是编译时常量或运行时常量,这取决于初始值是否是编译时常量表达式。在某些情况下,很难区分。
例如:
int x { 5 }; // 非 const
const int y { x }; // 运行时常量 (因为使用非const变量初始化)
const int z { 5 }; // 编译时常量
const int w { getValue() }; // 不是很容易判断
幸运的是,我们可以获得编译器的帮助,以确保在需要的地方获得编译时常量。为此,我们在变量的声明中使用constexpr关键字而不是const。constexpr(“常量表达式”的缩写)变量只能是编译时常量。如果constexpr变量的初始化值不是常量表达式,编译器将报错。
#include <iostream>
int six() {
return 6;
}
int main() {
constexpr double num{ 9.0 };
constexpr int i{ 2 + 1 };
constexpr int j{ i };
constexpr int f{ six() }; // 编译报错
return 0;
}
- 普通函数调用在运行时求值。这意味着函数参数即使是编译时常量,也会被视为运行时常量。
- 字面值常量:
- 字面值是直接插入到代码中的值。
- 所有字面值都有类型。字面值的类型是从字面值的值中推导出来的。例如,作为整数(例如5)的字面值被推断为int类型。
- 默认情况下,浮点字面值的类型为double。要使它们成为float字面值,应使用F(或f)后缀。
- “Hello,world” 是一个字符串字面值。这样的字符串通常称为C字符串或C样式的字符串,因为它们是从C语言继承的。所有的C字符串,都有一个隐式的null结尾符。例如"hello",看起来只有五个字符,但实际是6个 ‘h’, ’e’, ’l‘, ’l’, ‘o’, and ‘\0’ (ASCII 码 0)。不像其它大多数字面值常量(它们都是值,而非对对象),c字符串是一个const的对象,程序开始执行前会确保存在,直到程序结束。与C样式字符串字面值不同,std::string和std::string_view字面值会创建临时对象。这些临时对象会立即使用,在创建它们的完整表达式的末尾被销毁。
数据类型
...
头文件保护
使用头文件,很容易导致头文件中的定义被多次包含。当头文件#include另一个头文件(这是常见的)时,可能会发生这种情况。好消息是,我们可以通过一种称为头文件保护(也称为包含保护)的机制来避免上述问题。头文件保护的目标是防止代码文件引入头文件的多个副本
square.h
#ifndef SQUARE_H
#define SQUARE_H
int getSquareSides()
{
return 4;
}
#endif // !SQUARE_H
wave.h
#ifndef WAVE_H
#define WAVE_H
#include "square.h"
#endif // !WAVE_H
main.cpp
#include "square.h"
#include "wave.h"
#include <iostream>
int main()
{
std::cout << "测试头文件保护";
return 0;
}
头文件保护不会阻止头文件在不同文件只出现一次。根据设计,头文件保护不会阻止给定的头文件被包含到不同的代码文件中。这也可能导致意外问题。解决此问题的最佳方法是将函数定义放在其中一个.cpp文件中,以便头文件仅包含向前声明。
square.h
#ifndef SQUARE_H
#define SQUARE_H
int getSquareSides(); //向前声明
int getSquarePerimeter(int sideLength); //向前声明
#endif // !SQUARE_H
square.cpp
#include "square.h"
int getSquareSides()
{
return 4;
}
int getSquarePerimeter(int sideLength) {
return sideLength * getSquareSides();
}
main.cpp
#include "square.h"
#include "wave.h"
#include <iostream>
int main()
{
std::cout << "测试头文件保护";
return 0;
}
现代编译器使用#pragma预处理器指令支持更简单的替代形式的头文件保护:一些开发公司(如Google)建议使用传统的头文件保护。
#pragma once
// your code here
运算符
#include <iostream>
using namespace std;
int main() {
int a = 20;
int b = 10;
int c;
c = a > b ? a : b;
cout << "c=" << c << endl;
system("pause");
return 0;
}
程序流程结构
if
#include <iostream>
using namespace std;
int main() {
int score = 0;
cout << "请输入一个数字:" << endl;
cin >> score;
if (score >= 6) {
cout << "合格了" << endl;
}
else {
cout << "不合格" << endl;
}
system("pause");
return 0;
}
for
#include <iostream>
using namespace std;
int main() {
int num = 0;
for (int i = 0; i < 10; i++)
{
if (i == 5) {
break;
}
cout << i << endl;
}
system("pause");
return 0;
}
goto
#include <iostream>
using namespace std;
int main() {
//可以无条件跳转到标记的位置
cout << "1" << endl;
goto FLAG;
cout << "2" << endl;
cout << "3" << endl;
cout << "4" << endl;
FLAG:
cout << "5" << endl;
system("pause");
return 0;
}
while
#include <iostream>
using namespace std;
int main() {
int num = 0;
while (num < 10)
{
cout << num << endl;
num++;
}
system("pause");
return 0;
}
数组
#include <iostream>
using namespace std;
int main() {
//一维数组
int arr1[5];
int arr2[10] = { 1,2,3 };
int arr3[] = { 4,5,6,7 };
for (int i = 0; i < sizeof(arr2) / sizeof(arr2[0]); i++)
{
cout << arr2[i] << endl;
}
cout << "整个数组所占内存空间为:" << sizeof(arr2) << endl;
cout << "每个元素所占内存空间为:" << sizeof(arr2[0]) << endl;
cout << "数组的元素个数为:" << sizeof(arr2) / sizeof(arr2[0]) << endl;
//通过数组名获取数组首地址,默认为十六进制,强转为int就是十进制
cout << "数组首地址为:" << (int)arr2 << endl;
cout << "数组第2个元素的地址为:" << (int) & arr2[1] << endl;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main() {
//二维数组定义的四种方式
int arr[2][3];
int arr2[3][2] = { {1,2},{3,4} };
int arr3[2][2] = { 5,6,7,8 };
int arr4[][3] = { 9,10,11,12,13,14 };
//查看二维数组所占内存空间
cout << "二维数组大小:" << sizeof(arr) << endl;
cout << "二维数组一行大小:" << sizeof(arr[0]) << endl;
cout << "二维数组元素大小:" << sizeof(arr[0][0]) << endl;
cout << "二维数组行数:" << sizeof(arr) / sizeof(arr[0]) << endl;
//获取二维数组首地址
cout << (int)arr << endl;
//二维数组第一行首地址
cout << (int)arr[0] << endl;
//第二行首地址
cout << (int)arr[1] << endl;
//二维数组第一个元素地址
cout << (int)&arr[0][0] << endl;
system("pause");
return 0;
}
函数
#include "sayHi.h"
void sayHi() {
cout << "嗨!!!" << endl;
}
#include <iostream>
#include "sayHi.h"
using namespace std;
int add(int sum1, int sum2) {
return sum1 + sum2;
}
void sayHello(string name) {
cout << "你好," << name << endl;
}
void sayBy(int year);
int main() {
/*
* 1.函数语法:
* 返回值类型 函数名 (参数列表){
* 函数体语句
* return 表达式
* }
* 2.如果函数不需要返回值,声明的时候可以写 void
* 3. 所谓值传递,就是函数调用时实参将数值传入给形参,如果形参在调用函数期间发生改变,
并不会影响实参
* 4. 函数的声明与定义:函数的声明可以多次,但是函数的定义只能有一次。
* 5. 函数的分文件编写:
* 1)创建后缀名为.h的头文件
* 2)创建后缀名为.cpp的源文件
* 3)在头文件中写函数的声明;在源文件中写函数的定义
* 4)在使用函数的源文件中引入头文件
*/
cout << add(1, 2) << endl;
sayHello("张三");
sayBy(1998);
sayHi();
system("pause");
return 0;
}
void sayBy(int year){
cout << year << ",拜拜" << endl;
}
自定义命名空间和作用域解析操作符
- C++允许我们通过namespace关键字定义自己的命名空间。在程序中创建的命名空间称为用户定义命名空间。
- 建议以大写字母开始命名空间名称。
- 使用域解析操作符( :: )访问命名空间。
- 域解析操作符也可以在标识符之前使用,而不提供命名空间名称(例如 ::doSomething)。在这种情况下,在全局命名空间中查找标识符。
- 如果使用命名空间内的标识符,并且没有提供域解析,编译器将首先尝试在同一命名空间中查找匹配的声明。如果没有找到匹配的标识符,编译器将依次检查外围每个层级的命名空间,以查看是否找到匹配,直到检查全局命名空间。
- 对于命名空间内的标识符,前向声明也需要在同一命名空间内。
- 在多个位置(跨多个文件或同一文件中的多个位置)声明命名空间块是合法的。命名空间中的所有声明都被视为命名空间的一部分。
- 命名空间可以嵌套在其他命名空间中。
- 由于在嵌套命名空间中键入变量或函数的限定名可能会很痛苦,C++允许您创建命名空间别名。
add.h
#pragma once
namespace BasicMath {
int add(int a, int b);
}
subtract.h
#pragma once
namespace BasicMath {
int sub(int x, int y);
}
add.cpp
#include "add.h"
namespace BasicMath {
int add(int a, int b) {
return a + b;
}
}
subtract.cpp
#include "subtract.h"
namespace BasicMath {
int sub(int x, int y) {
return x - y;
}
}
Main.cpp
#include <iostream>
#include "add.h"
#include "subtract.h"
void print() {
std::cout << "全局的输出方法" << std::endl;
}
namespace Dog {
void print() {
std::cout << "小狗画梅花" << std::endl;
}
void action() {
std::cout << "狗在叫" << std::endl;
print();
::print(); // 调用 全局命名空间中的 print()
}
}
namespace Cat {
void action() {
std::cout << "猫在跑步" << std::endl;
}
}
namespace Child {
namespace Eat {
void eatApple() {
std::cout << "孩子吃苹果" << std::endl;
}
}
}
int main() {
/*Dog::action();
Cat::action();*/
/*print();
::print();
Dog::print();*/
//Dog::action();
int res = BasicMath::add(1, 2);
std::cout << res << "\n";
std::cout << BasicMath::sub(2, 1) << std::endl;
//Child::Eat::eatApple();
namespace AboutChild = Child::Eat;
AboutChild::eatApple();
system("pause");
return 0;
}
类型转换和static_cast
#include <iostream>
void print(int x) {
std::cout << x << std::endl;
}
int main() {
//print(5.5); // warning C4244: “参数”: 从“double”转换到“int”,可能丢失数据
//double d{ 5 };
//int x{ 5.5 };//error
//显式类型转换 static_cast<新类型>(表达式)
print(static_cast<int>(5.5));
//将char转为int
char ch{ 97 };
std::cout << ch << '\n'; //a
std::cout << static_cast<int>(ch) << '\n'; //97
//将无符号数字转换为有符号数字(如果要转换的值不适合新类型的范围,static_cast操作符将产生未定义的行为。)
unsigned int u{ 5 };
int s{ static_cast<int>(u) };
std::cout << s << '\n';
return 0;
}
指针
#include <iostream>
using namespace std;
int main() {
/*
* 1.可以通过指针间接访问内存
* 2.内存编号是从0开始记录的,一般用十六进制表示
* 3.可以利用指针变量保存地址
* 4.指针变量定义语法:数据类型 * 变量名;
*/
int a = 10;
int* p;
p = &a;
cout << "a的地址为:" << &a << endl;
cout << "指针p:" << p << endl;
//通过解引用的方式找到指针指向的内存
*p = 50;
cout << "p指向的内存中的数据:" << *p << endl;
cout << "a:" << a << endl;
//指针所占用的内存空间(64位操作系统占用8个字节,32位操作系统占用4字节)
cout << "p所占用的内存空间:" << sizeof(p) << endl;
cout << "double类型指针所占用的内存空间:" << sizeof(char *) << endl;
cout << "string类型指针所占用的内存空间:" << sizeof(string *) << endl;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
void swap1(int c, int d);
void swap2(int* p1, int* p2);
void arrSortAsc(int* p,int len);
int main() {
/*
* 1.空指针:指针变量指向内存中编号为0的空间
* 用途:初始化指针变量
* 注意:空指针指向的内存是不可以访问的。
* 2.野指针:指针变量指向非法的内存空间。
* 3.const修饰指针:
* 1)const修饰指针:常量指针,指针的指向可以改,但是指针指向的值不可以改
* 2)const修饰常量:指针常量,指针的指向不可以改,但是指针指向的值可以改
* 3)const既修饰指针,又修饰常量。指针的指向和指针指向的值都不可以改
* 4.利用指针访问数组中的元素
* 5.利用指针作为函数参数,可以修改实参的值!
*/
//int* p = NULL;
//内存编号0~255为系统占用内存,不允许用户访问
//cout << *p << endl;
//野指针
/*int* p = (int *)0x1100;
cout << *p << endl;*/
//常量指针
int a = 10;
int b = 30;
const int* p = &a;
//*p = 20;//错误
p = &b;
//指针常量
int* const p2 = &a;
//p2 = &b; //错误
*p2 = 100;
//const修饰指针和常量
const int* const p3 = &a;
/*p3 = &b;
*p3 = 200;*/
//利用指针来访问数组中的元素
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << "数组中第一个元素:" << arr[0] << endl;
int* p4 = arr;
cout << "利用指针访问数组第一个元素:" << *p4 << endl;
cout << "数组中每个元素的大小为:" << sizeof(arr[0]) << endl;
//指针向后偏移四个字节
//p4++;
//cout << "偏移后的结果:" << *p4 << endl;
/*cout << "利用指针遍历数组" << endl;
for (int i = 0; i < 10; i++)
{
cout << *p4 << endl;
p4++;
}*/
int c = 13;
int d = 16;
swap1(c, d);
cout << "c=" << c << "d=" << d << endl;
swap2(&c, &d);
cout << "c=" << c << "d=" << d << endl;
//封装一个函数,利用冒泡排序,实现对整型数组的升序排序
int arr2[5] = { 4,2,5,1,3 };
int* p5 = arr2;
int len = sizeof(arr2) / sizeof(arr2[0]);
arrSortAsc(p5,len);
for (int i = 0; i < 5; i++)
{
cout << arr2[i] << endl;
}
system("pause");
return 0;
}
//值传递
void swap1(int c, int d) {
int temp = c;
c = d;
d = temp;
}
//地址传递
void swap2(int* p1, int* p2) {
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void arrSortAsc(int* p,int len) {
for (int i = 0; i < len-1; i++)
{
for (int j = 0; j < len-1-i ; j++)
{
if (p[j] > p[j + 1]) {
int temp = p[j + 1];
p[j + 1] = p[j];
p[j] = temp;
}
}
}
}
结构体
#include <iostream>
using namespace std;
struct User
{
//成员列表
string name;
int age;
int score;
}s3; //顺便创建结构体变量
void printUser(User user) {
cout << "(值传递)用户的姓名:" << user.name << endl;
}
void printUser2(const User* user) {
//user->age = 13; //操作失败,因为加了const
cout << "(地址传递)用户的姓名:" << user->name << endl;
}
int main() {
/*
* 1.结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
* 2.语法:struct 结构体名 {结构体成员列表};
* 3.通过结构体创建变量的方式有三种:注意:struct关键字可以省略不写
* 1)struct 结构体名 变量名;
* 2)struct 结构体名 变量名 = {成员1值,成员2值};
* 3)定义结构体时顺便创建变量(不建议)
* 4.结构体数组
* 1)作用:将自定义的结构体放入到数组中方便维护
* 2)语法:struct 结构体名 数组名[元素个数] = {{},{},...{}};
* 5.结构体指针
* 1)通过指针访问结构体中的成员
* 2)利用操作符 -> 可以通过结构体指针访问结构体属性
* 6.结构体嵌套:结构体中的一个成员可以是另一个结构体
* 7.结构体做函数参数
* 1)作用:将结构体作为参数向函数中传递,传递方式有两种
* 1)值传递
* 2)地址传递
* 8.结构体中const使用场景:用const来防止误操作
*/
struct Student
{
//成员列表
string name;
int age;
int score;
}s3; //顺便创建结构体变量
Student s1;
s1.name = "汤姆";
s1.age = 89;
s1.score = 100;
cout << "s1的名字:" << s1.name << endl;
Student s2 = { "tom",23,200};
cout << "s2的分数:" << s2.score << endl;
s3.name = "杰瑞";
s3.age = 8;
s3.score = 300;
cout << "s3的分数:" << s3.score << endl;
//结构体数组
Student arr[3] = {
{ "张三",13,400},
{ "李四",42,500},
{ "王五",51,600}
};
//给结构体数组中的元素赋值
arr[0].name = "张三2";
arr[2].age = 3;
//遍历结构体数组
cout << "==================" << endl;
for (int i = 0; i < 3; i++)
{
cout << "姓名:" << arr[i].name << endl;
cout << "年龄:" << arr[i].age << endl;
cout << "分数:" << arr[i].score << endl;
}
//结构体指针
cout << "==================" << endl;
Student* p = &s2;
cout << p->name << endl;
//结构体嵌套
cout << "==================" << endl;
struct Teacher
{
int id;
string name;
int age;
Student stu;
};
Student s4 = { "学生甲",23,110 };
Teacher t1 = { 001,"老师甲",34,s4 };
cout << "老师的姓名:" << t1.name << endl;
cout << "老师的学生的姓名:" << t1.stu.name << endl;
//结构体做函数参数
cout << "==================" << endl;
User u1 = { "tomla",23,100 };
//值传递
//printUser(u1);
//地址传递
printUser2(&u1);
system("pause");
return 0;
}
引用
new关键字
#include <iostream>
using namespace std;
int* func() {
//在堆区创建一个数据,new返回的是该数据类型的指针
int* p = new int(10);
return p;
}
void test01() {
int* p = func();
cout << *p << endl;
delete p;
cout << *p << endl;
}
void test02() {
//在堆区开辟一个数组,数组有五个元素
int* arr = new int[5];
for (int i = 0; i < 5; i++) {
arr[i] = i + 100;
}
for (int i = 0; i < 5; i++) {
cout << arr[i] << endl;
}
//释放堆区中的数组
delete[] arr;
}
int main() {
/*
* 1.C++中利用new操作符在堆区开辟数据:
* 语法:new 数据类型
* 2.堆区开辟的数据,由程序员手动开辟,手动释放利用操作符:delete 指针
* 3.利用new创建的数据,会返回该数据对应的类型的指针
* 4.释放堆区中的数组时要加[],例如:delete[] 指针;
*/
//test01();
test02();
system("pause");
return 0;
}
引用
#include <iostream>
using namespace std;
//两个数字交换
//地址传递
void swapByAddr(int* a, int* b) {
int temp;
temp = *b;
*b = *a;
*a = temp;
}
//引用传递
void swapByRefer(int &a,int &b) {
int temp = a;
a = b;
b = temp;
}
int& test01() {
int a = 10;
return a;
}
int& test02() {
static int a = 10; //静态变量,存放在全局区,全局区上的数据由系统释放
return a;
}
int main() {
/*
* 1.引用的作用:给变量起别名
* 2.语法:数据类型 &别名 = 原名
* 3.注意事项:1)引用必须初始化;2)引用在初始化后,不可以改变
* 4.引用做函数参数:
* 1)函数传参时,可以利用引用让形参修饰实参
* 2)可以简化指针修改实参
* 5.引用做函数返回值
* 1)注意:不要返回局部变量引用
* 2)用法:函数调用作为左值(如果函数的返回值是引用,这个函数调用可以作为左值)
*/
//int a = 10;
////创建引用
//int& b = a;
//cout << a << endl;
//cout << b << endl;
//b = 20;
//cout << a << endl;
//cout << b << endl;
/*int a = 50;
int b = 80;
swapByAddr(&a, &b);
cout << "a=" << a << endl;
cout << "b=" << b << endl;
swapByRefer(a, b);
cout << "a=" << a << endl;
cout << "b=" << b << endl;*/
int& ref = test01();
cout << ref << endl;
cout << ref << endl;
int& ref2 = test02();
cout << ref2 << endl;
test02() = 1000;
cout << ref2 << endl;
system("pause");
return 0;
}
引用的本质
#include <iostream>
using namespace std;
void func(int& a) {
//自动转换为 *a = 100;
a = 100;
}
int main() {
/*
* 1.引用的本质:在C++内部实现是一个指针常量
* 2.推荐使用引用,因为引用的本质是指针常量,所有指针操作,编译器都帮我们做了
*/
int a = 0;
//自动转换为 int* const ref = &a; 这也解释了为什么引用的指向不可更改
int& ref = a;
//自动转换为 *ref = 20;
ref = 20;
cout << a << endl;
func(a);
cout << a << endl;
system("pause");
return 0;
}
类和对象
基础用法
#include <iostream>
using namespace std;
class C1
{
//将成员属性设置为私有,自己控制读写权限
int m;
public:
void setM(int m){
this->m = m;
}
int getM(){
return m;
}
};
struct C2
{
int m;
};
int main()
{
//struct默认权限是 public
//class默认权限是 private
C1 c1;
// c1.m
C2 c2;
c2.m = 10;
c1.setM(20);
cout << c1.getM() << endl;
system("pause");
return 0;
}
析构函数
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "aaaa" << endl;
}
~Person(){
cout << "bbbb" << endl;
}
};
void test01(){
Person p;
}
int main()
{
// 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
/**
* 1.析构函数的声明:~类名(){}
* 2.析构函数不可以有参数,因此不可以发生重载
* 3.构造函数和析构函数都是必须的,如果我们不提供,则编译器会提供空的构造和析构实现
*/
test01();
system("pause");
return 0;
}
构造函数的分类
- 有参和无参构造函数
- 按类型:普通构造和拷贝构造
class Person
{
public:
Person()
{
cout << "无参构造函数Person()" << endl;
}
Person(int a)
{
cout << "有参构造函数Person(int a)" << endl;
age = a;
}
~Person()
{
cout << "~Person()" << endl;
}
//拷贝构造函数
Person(const Person &p){
//将传入的p身上的属性拷贝到当前对象的age身上
age = p.age;
}
int age;
};
构造函数的调用方式
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "无参构造函数Person()" << endl;
}
Person(int a)
{
cout << "有参构造函数Person(int a)" << endl;
age = a;
}
~Person()
{
cout << "~Person()" << endl;
}
//拷贝构造函数
Person(const Person &p){
//将传入的p身上的属性拷贝到当前对象的age身上
age = p.age;
}
int age;
};
void test01(){
//括号法(注意:调用无参构造函数时,不要加括号,否则编译器会认为这是一个函数的声明,例如Person p();)
Person p; //调用无参构造函数
Person p1(10); //调用有参构造函数
Person p3(p1);
cout << "p3的年龄:" << p3.age << endl;
//显示法 (注意:不要利用拷贝构造函数来初始化匿名对象,例如Person(p3),编译器会认为这是一个对象的声明,等同于Person p3;)
Person p1;
Person p2 = Person(10); //有参构造 Person(10)单拿出来为匿名对象,特点:当前行执行结束后,系统会立即回收掉匿名对象
Person p3 = Person(p2); //拷贝构造
//隐式转换法
Person p4 = 10; //相当于 Person p4 = Person(10);
Person p5 = p4; //拷贝构造
}
int main()
{
test01();
system("pause");
return 0;
}
拷贝构造函数调用时机
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person()" << endl;
}
Person(int age)
{
cout << "Person(int age)" << endl;
m_age = age;
}
~Person()
{
cout << "~Person()" << endl;
}
Person(const Person &p)
{
cout << "Person(const Person &p)" << endl;
m_age = p.m_age;
}
int m_age;
};
void test01()
{
// 1. 使用一个已经创建完毕的对象来初始化一个新对象
Person p1(20);
Person p2(p1);
cout << "p2.age = " << p2.m_age << endl;
}
void doWork(Person p){
}
void test02(){
// 2. 值传递的方式给函数参数传值
Person p;
doWork(p);
}
Person doWork02(){
Person p1;
cout << &p1 << endl;
return p1;
}
void test03(){
// 3. 以值方式返回局部对象(实测并没有调用拷贝构造函数)
Person p = doWork02();
cout << &p << endl;
}
int main()
{
/*
拷贝构造函数调用时机的三种情况:
1. 使用一个已经创建完毕的对象来初始化一个新对象
2. 值传递的方式给函数参数传值
3. 以值方式返回局部对象
*/
// test01();
// test02();
test03();
system("pause");
return 0;
}
构造函数调用规则
- 默认情况下,编译器至少给一个类添加三个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造函数
- 如果用户定义拷贝构造函数,c++不再提供其他的普通构造函数了,但是会提供默认析构函数
浅拷贝与深拷贝
- 浅拷贝:简单的赋值操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
- 浅拷贝带来的问题:对象p1在堆区存放的数据,使用默认的拷贝构造函数进行的是浅拷贝,即把堆区的内存地址赋值给了p2,因此在析构函数中释放堆区内存时,p1和p2释放的是同一个地址的内存,p2释放之后,p1就无法释放。解决方案是使用深拷贝,自定义一个拷贝构造函数,p2在堆区重新开辟空间,将p1的内容拷贝到新的堆区内存中,这样p1和p2指向的堆内存不同,但是内容相同。
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person无参构造函数" << endl;
}
Person(int age, int height)
{
m_Age = age;
// 将数据创建在堆区
m_Height = new int(height);
cout << "Person有参构造函数" << endl;
}
~Person()
{
// 析构中将堆区开辟的数据释放掉
//浅拷贝带来的问题:堆区的内存重复释放
//解决方案:使用深拷贝,p1和p2各指向一个堆区数据,这两个堆区存放的数据是相同的,但指针指向的内存是不一样的
if(m_Height != NULL){
delete m_Height;
m_Height = NULL;
}
cout << "Person析构函数" << endl;
}
//自己实现拷贝构造函数
Person(const Person &p){
cout << "Person拷贝构造函数被调用了" << endl;
m_Age = p.m_Age;
// m_Height = p.m_Height; //这是编译器的默认实现
m_Height = new int(*p.m_Height);
}
int m_Age;
int *m_Height;
};
void test01()
{
Person p1(10, 170);
cout << "p1的年龄为:" << p1.m_Age << "身高为:" << *p1.m_Height << endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
初始化列表
#include <iostream>
using namespace std;
class Person
{
public:
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;
};
void test(){
Person p(10, 20, 30);
cout << p.m_A << endl;
}
int main()
{
/*
C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)...{};
*/
test();
system("pause");
return 0;
}
对象成员
#include <iostream>
using namespace std;
class Phone
{
public:
Phone(string pName)
{
cout << "Phone(string pName)" << endl;
name = pName;
}
~Phone(){
cout << "~Phone()" << endl;
}
string name;
};
class Person
{
public:
// 相当于 Phone phone = pName; //隐式转化法
Person(string name, string pName) : name(name), phone(pName)
{
cout << "Person(string name, string pName)" << endl;
}
~Person(){
cout << "~Person()" << endl;
}
string name;
Phone phone;
};
void test()
{
Person p("tom", "apple");
cout << p.name << ":" << p.phone.name << endl;
}
int main()
{
/*
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
*/
test();
system("pause");
return 0;
}
静态成员
#include <iostream>
using namespace std;
class Person
{
public:
static int m_A;
static void func() {
cout << "static void func()..." << endl;
cout << "m_A=" << m_A << endl;
//cout << "m_C=" << m_C << endl;
cout << "m_B=" << m_B << endl;
}
private:
int m_C;
static int m_B;
static void func2() {
cout << "static void func2()" << endl;
}
};
int Person::m_A = 10;
int Person::m_B = 20;
void test01() {
Person p;
cout << "p.m_A = " << p.m_A << endl;
Person p2;
p2.m_A = 20;
cout << "p2.m_A = " << p2.m_A << endl;
}
void test02() {
//通过对象进行访问
Person p;
cout << p.m_A << endl;
//通过类名进行访问
cout << Person::m_A << endl;
//cout << Person::m_B << endl; //无法访问
}
void test03() {
//通过对象调用
Person p;
p.func();
//通过类名调用
Person::func();
//Person::func2(); //无法访问
}
int main()
{
/*
静态成员就是在成员变量和成员函数前加关键字static,称为静态成员
1. 静态成员变量: 所有对象都共享同一份数据; 编译阶段就分配内存; 类内声明,类外初始化操作。
访问方式:
1. 通过对象进行访问
2. 通过类名进行访问
静态成员变量也是有访问权限的
静态成员变量需要初始化后才能被访问到!!!
2. 静态成员函数:静态成员函数只能访问静态成员变量;也是有两种访问方式
*/
//test01();
// test02();
test03();
system("pause");
return 0;
}
成员变量和成员函数是分开存储的
#include <iostream>
using namespace std;
class Person
{
int m_A; //非静态成员变量,是属于类的对象上的
static int m_B; //静态成员变量,不属于类的对象上
//非静态成员函数,不属于类的对象上
void func(){
cout << "Person::func()" << endl;
}
//静态成员函数,不属于类的对象上
static void func2(){
cout << "Person::func2()" << endl;
}
};
int Person::m_B = 10;
void test01()
{
Person p;
//空对象占用的内存空间为:1字节
//C++编译器会给每个空对象分配一个字节的空间,是为了区分对象占内存的位置
//每个空对象应该有一个独一无二的内存地址
cout << "sizeof(p):" << sizeof(p) << endl;
}
void test02(){
Person p;
// 占用的内存空间为:4字节
cout << "sizeof(p):" << sizeof(p) << endl;
}
int main()
{
/*
成员变量和成员函数是分开存储的
结论:
1. 只有非静态成员变量属于类的对象上
2. 空对象占用内存空间:1字节
*/
// test01();
test02();
system("pause");
return 0;
}
this指针概念
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
this -> age = age;
}
//注意:如果PersonAddAge函数的返回值是Person,以值的方式返回,就会拷贝一份新的对象(参考拷贝构造函数)
Person& PersonAddAge(Person &p){
this->age += p.age;
return *this;
}
int age;
};
void test01()
{
Person p1(12);
cout << "p1的年龄为:" << p1.age << endl;
}
void test02(){
Person p1(20);
Person p2(20);
p2.PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年龄为:" << p2.age << endl;
}
int main()
{
/*
this指针概念:
1. 每一个非静态成员函数只会有一份函数实例,也就是说多个同类型的对象会共用同一个函数实例,那么如何区分是哪个对象在调用呢?
2. C++通过提供特殊的对象指针this来区分,this指针指向被调用的成员函数所属的对象
3. this指针是隐含的,编译器自动给每一个非静态成员函数添加this指针作为参数
4. this指针的用途:
1)当形参和成员变量同名时,可用this指针来区分,例如 this->age = age;
2)在类的非静态成员函数中返回对象本身,可使用 return *this;
*/
// test01();
test02();
system("pause");
return 0;
}
空指针调用成员函数
#include <iostream>
using namespace std;
class Person
{
public:
void showClass(){
cout << "this is Person Class" << endl;
}
void showPersonAge(){
//age 相当于 this->age,报错原因是 this指针为空
if(this == NULL){
return;
}
cout << "age:" << age << endl;
}
int age;
};
void test01(){
Person *p = NULL;
p->showPersonAge();
p->showClass();
}
int main()
{
/*
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加判断以保证代码的健壮性
*/
system("pause");
return 0;
}
常函数和常对象
#include <iostream>
using namespace std;
class Person
{
public:
int m_A;
mutable int m_B;
// void showPerson() const 中的const等同于将this指针修改为:const Person* const this
void showPerson() const
{
// this指针的本质是指针常量,在这里是Person* const this,指针的指向是不可修改的
// m_A = 100;//错误 常函数不能修改成员变量的值
m_B = 100; // 正确 常函数中可以修改mutable修饰的成员变量的值
}
void func()
{
m_A = 100;
}
};
void test01()
{
Person p;
p.showPerson();
}
void test02()
{
const Person p; // 在对象前加const,对象称为常对象
// p.m_A = 100; //不可以
p.m_B = 100;
p.showPerson();
// p.func(); //不可以
}
int main()
{
/*
常函数:
1. 成员函数后加const我们称这个函数为常函数
2. 常函数不能修改成员变量的值
3. 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
1. 声明对象前加const称该对象为常对象
2. 常对象只能调用常函数
*/
test01();
test02();
system("pause");
return 0;
}
友元--全局函数做友元
#include <iostream>
using namespace std;
class Building
{
// goodGay()全局函数做友元
friend void goodGay(Building &building);
public:
string m_SittingRoom;
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
private:
string m_BedRoom;
};
// 全局函数
void goodGay(Building &building)
{
cout << "好基友全局函数正在访问:" << building.m_SittingRoom << endl;
cout << "好基友全局函数正在访问:" << building.m_BedRoom << endl;
}
void test01()
{
Building building;
goodGay(building);
}
int main()
{
/*
友元的目的:让一个函数或者类可以访问另一个类的私有成员
友元的关键字为 friend
友元的三种实现:
1. 全局函数做友元
2. 类做友元
3. 成员函数做友元
*/
test01();
system("pause");
return 0;
}
友元--类做友元
#include <iostream>
using namespace std;
class Building
//Good类是朋友,可以访问本类中私有成员
{
friend class GoodGay;
public:
string m_sittingRoom;
public:
Building();
private:
string m_bedRoom;
};
//类外写成员函数
Building::Building() {
m_sittingRoom = "客厅";
m_bedRoom = "卧室";
}
class GoodGay
{
public:
GoodGay();
void visit();
Building* building;
};
//GoodGay的构造函数
GoodGay::GoodGay() {
//创建建筑物
building = new Building;
}
//类外写成员函数
void GoodGay::visit() {
cout << "GoodGay类中的visit函数正在访问:" << building->m_sittingRoom << endl;
cout << "GoodGay类中的visit函数正在访问:" << building->m_bedRoom << endl;
}
void test01() {
GoodGay g1;
g1.visit();
}
int main()
{
/*
类做友元
*/
test01();
system("pause");
return 0;
}
友元--成员函数做友元
#include <iostream>
using namespace std;
class Building;
class GoodGay
{
public:
GoodGay();
Building* building;
// 让visit可以访问Building中的私有属性
void visit();
// 让visit2不可以访问Building中的私有属性
void visit2();
};
class Building
{
//告诉编译器,GoodGay下的visit函数作为本类友元
friend void GoodGay::visit();
public:
string m_SittingRoom;
Building();
private:
string m_bedRoom;
};
//类外实现成员函数
Building::Building() {
m_SittingRoom = "客厅";
m_bedRoom = "卧室";
}
GoodGay::GoodGay() {
building = new Building;
}
void GoodGay::visit() {
cout << "visit函数正在访问" << building->m_SittingRoom << endl;
cout << "visit函数正在访问" << building->m_bedRoom << endl;
}
void GoodGay::visit2() {
cout << "visit2函数正在访问" << building->m_SittingRoom << endl;
//cout << "visit2函数正在访问" << building->m_bedRoom << endl; //visit2不是Building类的友元
}
void test01() {
GoodGay gg;
gg.visit();
gg.visit2();
}
int main()
{
test01();
system("pause");
return 0;
}
加号运算符重载
#include <iostream>
using namespace std;
// 需求:通过成员函数重载加号运算符;通过全局函数重载加号运算符
class Person
{
public:
// Person operator+(Person &p){
// Person pTemp;
// pTemp.m_A = this->m_A + p.m_A;
// pTemp.m_B = this->m_B + p.m_B;
// return pTemp;
// }
int m_A;
int m_B;
};
//全局函数
Person operator+(Person &p1, Person &p2)
{
Person pTemp;
pTemp.m_A = p1.m_A + p2.m_A;
pTemp.m_B = p1.m_B + p2.m_B;
return pTemp;
}
Person operator+(Person& p1,int num){
p1.m_A += num;
p1.m_B += num;
return p1;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3;
p3 = p1 + p2;
//本质调用
// p3 = p1.operator+(p2);
// p3 = operator+(p1,p2);
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
Person p4 = p1 +5;
cout << "p4.m_A = " << p4.m_A << endl;
cout << "p4.m_B = " << p4.m_B << endl;
}
int main()
{
/**
* 运算符重载:
* 1.概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
* 加号运算符重载
* 作用:实现两个自定义数据类型相加的运算
*/
test01();
system("pause");
return 0;
}
左移运算符重载
#include <iostream>
using namespace std;
class Person
{
friend std::ostream& operator<<(std::ostream& out, Person& p);
private:
// 通常不会使用成员函数重载左移运算符
int m_A;
int m_B;
public:
Person(int a, int b) {
m_A = a;
m_B = b;
}
};
// 全局函数重载左移运算符
std::ostream& operator<<(std::ostream& out ,Person& p)
{
out << "m_A = " << p.m_A << endl;
out << "m_B = " << p.m_B << std::endl;
return out;
}
void test01()
{
Person p(10,20);
std::cout << p << "hello" << std::endl;
}
int main()
{
/**
* 左移运算符重载
* 作用:可以输出自定义数据类型
*/
test01();
system("pause");
return 0;
}
递增运算符重载
#include <iostream>
using namespace std;
class MyInteger
{
friend ostream& operator<<(ostream& cout, const MyInteger& myInt);
public:
MyInteger()
{
m_Num = 0;
}
//重载前置++运算符
MyInteger& operator++() {
m_Num++;
return *this;
}
//重载后置++运算符,int形参作为占位参数,为了满足函数重载的条件,参数列表不同
MyInteger operator++(int) {
//先记录当前值,再递增,然后将记录的值返回
MyInteger temp = *this;
//cout << &temp << endl;
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, const MyInteger& myInt)
{
cout << myInt.m_Num;
return cout;
}
void test01() {
MyInteger i1;
cout << ++(++i1) << endl;
cout << i1 << endl;
}
void test02() {
MyInteger myint;
cout << myint++ << endl;
//cout << &myint << endl;
cout << myint << endl;
}
int main()
{
/**
* 递增运算符重载
* 作用:通过重载递增运算符,实现自己的整型数据
*/
//test01();
test02();
system("pause");
return 0;
}
赋值运算符重载
关系运算符重载
函数调用运算符重载