一、结构体
结构体是一 群数据类型的集合。它也是一种数据类型。
(一)声明结构体
结构体的名字与类一样,首字母大写。
注意:声明结构体时必须使用struct 修饰,调用、创建结构体变量时可以省略。
- 方式一:
#include <iostream>
using namespace std;
struct Person {
int id = 0;
string name = "lihua";
int age = 18;
char sex = 'm';
};
int main() {
Person person1;
Person person2;
person1.name = "lihua";
person2.name = "xiaoming";
cout << person1.name << endl;
cout << person2.name << endl;
return 0;
}
- 方式二:
#include <iostream>
using namespace std;
typedef struct Person {
int id = 0;
string name = "lihua";
int age = 18;
char sex = 'm';
} Student;
int main() {
Student student1;
Student student2;
student1.name = "lihua";
student2.name = "xiaoming";
cout << student1.name << endl;
cout << student2.name << endl;
return 0;
}
(二)使用结构体
- 方式一:
#include <iostream>
using namespace std;
typedef struct Person {
int id = 0;
string name = "lihua";
int age = 18;
char sex = 'm';
} Student;
int main() {
Student student;
student.id = 123;
student.name = "lihua";
student.age = 18;
student.age = 'w';
cout << student.id << endl;
cout << student.name << endl;
cout << student.age << endl;
cout << student.sex << endl;
return 0;
}
- 方式二:
#include <iostream>
using namespace std;
typedef struct Person {
int id = 0;
string name = "lihua";
int age = 18;
char sex = 'm';
} Student;
int main() {
Student student = {123,"lihua",18,'w'};
cout << student.id << endl;
cout << student.name << endl;
cout << student.age << endl;
cout << student.sex << endl;
return 0;
}
(三)结构体数组
- 方式一:
#include <iostream>
using namespace std;
typedef struct Person {
int id = 0;
string name = "lihua";
int age = 18;
char sex = 'm';
} Student;
void printf(const Student &student) {
cout << student.id << endl;
cout << student.name << endl;
cout << student.age << endl;
cout << student.sex << endl;
}
int main() {
Student students[3] = {
{1, "a", 18, 'w'},
{2, "b", 19, 'm'},
{3, "c", 20, 'm'}
};
for (int i = 0; i < size(students); ++i) {
printf(students[i]);
}
return 0;
}
- 方式二:
#include <iostream>
using namespace std;
typedef struct Person {
int id = 0;
string name = "lihua";
int age = 18;
char sex = 'm';
} Student;
void printf(const Student &student) {
cout << "id = "<<student.id << " "<< "name = "<<student.name << " "<< "age = "<<student.age << " "<< "sex = "<<student.sex << endl;
}
void printf(Student *student) {
cout << "id = "<<student->id << " "<< "name = "<<student->name << " "<< "age = "<<student->age << " "<< "sex = "<<student->sex << endl;
}
int main() {
auto *s = new Student[3];
for (int i = 0; i < 3; ++i) {
printf(s);
s++;
}
return 0;
}
二、内存模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的。
注意:代码区的局部变量,不要返回它的指针和引用 。 - 全局区:存放全局变量和静态变量以及常量。
- 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
C++中在程序运行前分为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放 const修饰的全局常量 和 字符串常量
(一)new 关键字的使用
栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收。在C++中主要利用new在堆区开辟内存
new关键字可以在堆区中开辟内存。开辟后的内存程序不会自动释放,当程序结束运行后,操作系统才会回收。
#include <iostream>
#include <windows.h>
using namespace std;
string *useNew() {
int *ip = new int(3);
cout << *ip << endl;
float *fp = new float;
*fp = 9.9;
cout << *fp << endl;
int *array = new int[*ip]{1, 2, 3};
for (int j = 0; j < 3; ++j) {
cout << *array << endl;
array++;
}
string *str = new string("hello world");
cout << *str << endl;
return str;
}
int main() {
string *strP = useNew();
cout << *strP << endl;
cout << *strP << endl;
cout << *strP << endl;
return 0;
}
(二)delete 关键字的使用
delete 关键字可以释放有new开辟的内存空间。 释放多个地址,比如:数组 。使用delete [ ] p;
#include <iostream>
#include <windows.h>
using namespace std;
string *useNew() {
int *ip = new int(3);
cout << *ip << endl;
float *fp = new float;
*fp = 9.9;
cout << *fp << endl;
int *array = new int[*ip]{1, 2, 3};
for (int j = 0; j < 3; ++j) {
cout << *array << endl;
array++;
}
string *str = new string("hello world");
cout << *str << endl;
return str;
}
void useDelete(string *strP) {
delete strP;
}
int main() {
string *strP = useNew();
cout << *strP << endl;
cout << *strP << endl;
cout << *strP << endl;
cout << "地址:" << strP << endl;
useDelete(strP);
cout << *strP << endl;
int *arrayP = new int[3]{1,2,4};
delete [] arrayP;
return 0;
}
注释掉cout << *strP << endl;的情况下运行:
释放内存后继续调用该地址的指针。报错
(三)如何返回一个局部变量的地址
由C++的内存可以知道,局部变量在函数调用结束后会自动释放内存。因此,不要直接在函数中返回局部变量的地址。
#include <iostream>
#include <windows.h>
using namespace std;
int *test() {
int a = 10;
return &a;
}
int main() {
int *a = test();
cout << *a << endl;
cout << *a << endl;
cout << *a << endl;
cout << *a << endl;
return 0;
}
输出: 除了第一个打印的值外,其他的都是乱码。原因是,函数执行完后,会释放局部变量开辟的内存。至于第一个还能访问成功是c++为了防止用户意外调用,保留了内存的一次访问,之后就释放了内存。
想要返回一个局部变量的地址,必须使用new 关键字开辟空间,然后将这个变量返回。这样在函数调用结束后,才不会被回收内存。
int *test() {
int *a = new int(10);
return a;
}
三、引用的使用
- 引用可以简化指针的使用。
- 引用必须初始化,且初始化后,不能改变(指向的内存空间不会改变)。
- 引用的本质在c++内部实现是一个指针常量。发现是引用,转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改指向
语法: 数据类型 &别名 = 原名
#include <iostream>
using namespace std;
void test() {
int test1 = 1;
int test2 = 2;
int &d2 = test1;
d2 = test2;
cout << test1 << endl;
cout << d2 << endl;
}
int main() {
int a = 10;
int &d = a;
int *p = &a;
cout << &a << endl;
cout << &d << endl;
cout << p << endl;
d = 100;
cout << a << endl;
test();
return 0;
}
注意:引用与指针一样,不能返回局部引用。
#include <iostream>
using namespace std;
int &errorTest() {
int a = 10;
return a;
}
int &correctTest() {
static int a = 100;
return a;
}
int main() {
int &test1 = errorTest();
int &test2 = correctTest();
cout << test1 << endl;
cout << test1 << endl;
cout << test1 << endl;
cout << test2 << endl;
cout << test2 << endl;
cout << test2 << endl;
return 0;
}
常量引用 const int& ref = 10; 等价于 const int* const ref = &a; 常量指针常量。它的指向和指向的内容都是不可变的。
四、类
C++面向对象的三大特性为:封装、继承、多态。 封装性与java一致,有三种:public 公共权限、protected 保护权限、private 私有权限。
(一)创建类
创建类,一般在头文件中声明类的属性(变量),方法(函数)。 在源文件中实现类。具体如下: 注意:类名首字母大写。
- 头文件:Person.h
#include <iostream>
using namespace std;
#ifndef LEARN_01_PERSON_H
#define LEARN_01_PERSON_H
class Person {
private:
int id;
string name;
int age;
char sex;
public:
Person();
Person(int id, const string &name, int age, char sex);
virtual ~Person();
void say();
int getId() const;
void setId(int id);
const string &getName() const;
void setName(const string &name);
int getAge() const;
void setAge(int age);
char getSex() const;
void setSex(char sex);
};
#endif
- 源文件:Person.cpp
#include "Person.h"
void Person::say() {
cout << "我的名字是:" << name << endl;
}
Person::Person() {
cout << "调用无参构造函数,创建对象。" << endl;
}
Person::Person(int id, const string &name, int age, char sex) : id(id), name(name), age(age), sex(sex) {
cout << "调用有参构造函数,创建对象。" << endl;
}
Person::~Person() {
cout << "调用析构函数,对象已经销毁。" <<"析构的对象的id是:"<<this->id << endl;
}
int Person::getId() const {
return id;
}
void Person::setId(int id) {
Person::id = id;
}
const string &Person::getName() const {
return name;
}
void Person::setName(const string &name) {
Person::name = name;
}
int Person::getAge() const {
return age;
}
void Person::setAge(int age) {
Person::age = age;
}
char Person::getSex() const {
return sex;
}
void Person::setSex(char sex) {
Person::sex = sex;
}
(二)创建对象
- 方式一:像基本类型一样创建对象。类名 变量名; (默认调用无参构造器)
- 方式二:显示的调用构造器创建。
- 方式三:通过new关键字创建。
#include <iostream>
#include "Person.h"
using namespace std;
int main() {
auto *person = new Person();
person->setName("李华");
person->setId(1);
cout << "名字为:" << person->getName() << endl;
delete person;
Person person1;
person1.setId(2);
Person person2 = Person();
person2.setId(3);
Person person3 = Person(4, "李华", 18, 'm');
cout << "我的名字是:" << person3.getName() << ",今年:" << person3.getAge() << "岁。" << endl;
Person(5, "小明", 18, 'm').say();
return 0;
}
运行结果:
(三)销毁对象
局部(函数中)创建的对象,在函数结束运行后,自动调用析构函数就行销毁。
全局中的对象需要使用关键字delete 进行 销毁。比如:使用new关键字创建的对象。
(四)class与struct的区别
在C++中 struct和class区别就在于 默认的访问权限不同 区别:
- struct 默认权限为公共
- class 默认权限为私有
还有就是class类可以有函数。
(五)构造函数
构造函数两种分类方式:
- ? 按参数分为: 有参构造和无参构造
? 2. 按类型分为: 普通构造和拷贝构造
默认情况下,c++编译器至少给一个类添加3个函数:
-
默认构造函数(无参,函数体为空) -
默认析构函数(无参,函数体为空) -
默认拷贝构造函数,对属性进行值拷贝
构造函数默认规则如下:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造。
如果用户定义拷贝构造函数,c++不会再提供其他构造函数。
- 单参数构造函数,可以隐式调用。一般在构造函数前面 使用explicit 修饰,避免隐式调用。
下面是隐式调用的例子:
#include <iostream>
using namespace std;
class Test {
int i = 0;
public:
int p = 0;
string str = "hello";
Test(int p) : p(p) {}
Test(const string &str) : str(str) {}
};
int main() {
Test test = 100;
cout << test.p << endl;
string name = "lihua";
Test test1 = name;
cout << test.str << endl;
return 0;
}
- 自己创建有参构造函数后,c++编辑器不再提供无参的。
- 默认的拷贝构造函数(浅拷贝)
#include <iostream>
using namespace std;
class Test {
int i = 0;
public:
int p = 0;
string str = "hello";
Test(int p) : p(p) {}
Test(const string &str) : str(str) {}
};
int main() {
Test test = 100;
test.str = "拷贝";
cout << test.p << endl;
Test test2 = Test(test);
cout << test2.p << test2.str << endl;
return 0;
}
- 浅拷贝和深拷贝
#include <iostream>
using namespace std;
class Test {
int i = 0;
public:
int a = 0;
string str = "hello";
int *p = nullptr;
Test() {}
Test(int a) : a(a) {}
Test(const string &str) : str(str) {}
};
void copyTest() {
Test test1;
test1.a = 10;
test1.str = "test1";
test1.p = &test1.a;
Test test2 = Test(test1);
*test2.p = 200;
cout << "发现对象test1的a属性也发生了改变:" << *test1.p << endl;
}
int main() {
copyTest();
return 0;
}
demo2:
#include <iostream>
using namespace std;
class Teacher {
public:
int id = 0;
string name = "xiaohong";
int age = 18;
Teacher() {}
Teacher(int id, const string &name, int age) : id(id), name(name), age(age) {}
};
class Student {
public:
int id = 0;
string name = "lihua";
int age = 18;
Teacher *teacher = nullptr;
};
void copyTest() {
Student student1;
student1.id = 1;
student1.name = "小明";
student1.age = 18;
Teacher teacher =Teacher(2, "李老师", 45);
student1.teacher = &teacher;
Student student2 = Student(student1);
student2.teacher->name = "黄老师";
cout << student1.teacher->name << endl;
}
int main() {
copyTest();
return 0;
}
想要实现深拷贝很简单,只需要我们自定义拷贝函数即可。
#include <iostream>
using namespace std;
class Teacher {
public:
int id = 0;
string name = "xiaohong";
int age = 18;
Teacher() {}
Teacher(int id, const string &name, int age) : id(id), name(name), age(age) {}
};
class Student {
public:
int id = 0;
string name = "lihua";
int age = 18;
Teacher *teacher = nullptr;
Student() {}
Student(const Student &student) {
id = student.id;
name = student.name;
age = student.age;
teacher = new Teacher(student.teacher->id, student.teacher->name, student.teacher->age);
}
~Student() {
}
};
void copyTest() {
Student student1;
student1.id = 1;
student1.name = "小明";
student1.age = 18;
Teacher teacher = Teacher(2, "李老师", 45);
student1.teacher = &teacher;
Student student2 = Student(student1);
student2.teacher->name = "黄老师";
cout << student1.teacher->name << endl;
}
int main() {
copyTest();
return 0;
}
(六)关键字friend(友元)
friend 关键字与java里面的反射有点相似的地方,那就是能访问封装类型为私有的属性和方法。 在类中声明哪些类、方法能访问自己的私有属性和方法。
一共有三种方式:
- 全局函数做友元:全局函数中能调用该类的私有属性和方法。
- 类做友元:类中能调用其他类的私有属性和方法。
- 成员函数做友元:成员函数中能调用其他类的私有属性和方法。
#include <iostream>
using namespace std;
class Student;
class Teacher;
class Student {
friend void global(Student & student);
friend class Teacher;
private:
int money = 100;
void myMoney() const {
cout << "我有这么多钱:" << money << endl;
}
public:
int id;
string name;
int age;
};
class Teacher{
private:
int id;
string name;
int age;
public:
void getStudent(){
Student student;
student.myMoney();
}
};
void global(Student & student){
cout << student.money<< endl;
student.myMoney();
}
class Test2{
public:
void getTest1();
};
class Test1{
friend void Test2::getTest1();
private:
int a = 10;
};
void Test2::getTest1(){
Test1 test1;
cout <<"方式三:"<<test1.a <<endl;
}
int main() {
Student student;
global(student);
Teacher teacher;
teacher.getStudent();
Test2 test2;
test2.getTest1();
return 0;
}
(七)对象关系运算符重载 operator关键字的使用
让对象之间也能进行相加,比较等操作。
#include <iostream>
#include <utility>
using namespace std;
class Person {
private:
int id = 0;
int age = 18;
int money = 100;
string name = "lihua";
public:
Person() = default;
Person(int id, int age, int money, string name) : id(id), age(age), money(money), name(std::move(name)) {}
Person operator+(const Person &p) const {
Person temp;
temp.id = id + p.id;
temp.age = age + p.age;
temp.money = money + p.money;
temp.name = name +"-"+ p.name;
return temp;
}
bool operator==(const Person &p) const {
return money == p.money;
}
int getId() const {
return id;
}
void setId(int id) {
Person::id = id;
}
int getAge() const {
return age;
}
void setAge(int age) {
Person::age = age;
}
int getMoney() const {
return money;
}
void setMoney(int money) {
Person::money = money;
}
const string &getName() const {
return name;
}
void setName(const string &name) {
Person::name = name;
}
};
void printf(Person &person){
cout << "id="<<person.getId()<<" ;"<< "age="<<person.getAge()<<" ;"<< "name="<<person.getName()<<" ;"<< "money="<<person.getMoney()<<endl;
}
int main() {
Person person1 = Person(1, 18, 100, "li");
Person person2 = Person(2, 20, 100, "feng");
Person p = person1 + person2;
printf(p);
if (person1 == person2) {
cout << "两者的钱一样" << endl;
}
return 0;
}
更多可以参考这篇文章
(八)继承
1. 如何继承
类继承的格式 class 子类 : public 父类 ; public 表示继承父类那些封装类型的属性和方法,也就是继承类型。一共有三种继承类型:公共继承、保护继承、私有继承。
#include <iostream>
using namespace std;
class Father {
private:
string hobby;
public:
string name;
int age;
int money;
void say() const {
cout << "我的名字是" << name << endl;
}
};
class Son : public Father {
private:
string hobby;
string school;
public:
void mySchool() {
cout << "我在" << school << "上学。" << endl;
}
const string &getHobby() const {
return hobby;
}
void setHobby(const string &hobby) {
Son::hobby = hobby;
}
const string &getSchool() const {
return school;
}
void setSchool(const string &school) {
Son::school = school;
}
};
int main() {
Son son;
son.name = "小明";
son.money = 0;
son.age = 18;
son.say();
son.setSchool("星星小学");
son.mySchool();
return 0;
}
2. 继承类型
三种继承方式:
- 公共继承:子类内只能访问父类封装类型为 public 的属性和方法。同时子类继承到的属性方法的封装类型变为:public。
- 保护继承:子类内能访问父类封装类型为 public和protected 的属性和方法。同时子类继承到的属性方法的封装类型变为:protected。
- 私有继承:子类内能访问父类封装类型为 public和protected 的属性和方法。同时子类继承到的属性方法的封装类型变为:private。
- public
#include <iostream>
using namespace std;
class Father {
private:
string hobby;
const string &getHobby() const {
return hobby;
}
protected:
string house;
const string &getHouse() const {
return house;
}
public:
string name;
int age;
int money;
void say() const {
cout << "我的名字是" << name << endl;
}
};
class Son : public Father {
public:
void test(){
Son son;
}
};
int main() {
Son son;
return 0;
}
- protected
#include <iostream>
using namespace std;
class Father {
private:
string hobby;
const string &getHobby() const {
return hobby;
}
protected:
string house;
const string &getHouse() const {
return house;
}
public:
string name;
int age;
int money;
void say() const {
cout << "我的名字是" << name << endl;
}
};
class Son : protected Father {
public:
void test(){
Son son;
}
};
- private
#include <iostream>
using namespace std;
class Father {
private:
string hobby;
const string &getHobby() const {
return hobby;
}
protected:
string house;
const string &getHouse() const {
return house;
}
public:
string name;
int age;
int money;
void say() const {
cout << "我的名字是" << name << endl;
}
};
class Son : private Father {
public:
void test(){
Son son;
son.
}
};
3. 继承后,子类和父类的构造函数和析构函数调用顺序
对于构造函数,先调用父类再调用子类。
#include <iostream>
using namespace std;
class Father {
private:
string hobby;
const string &getHobby() const {
return hobby;
}
protected:
string house;
const string &getHouse() const {
return house;
}
public:
string name;
int age;
int money;
Father() {
cout << "父类构造函数。" << endl;
}
virtual ~Father() {
cout << "父类析构函数。" << endl;
}
void say() const {
cout << "我的名字是" << name << endl;
}
};
class Son : private Father {
public:
Son() {
cout << "子类构造函数。" << endl;
}
virtual ~Son() {
cout << "子类析构函数。" << endl;
}
};
int main() {
Son son;
return 0;
}
4. 重写父类的函数(函数、变量重名)
- 当子类有函数与父类一样时,子类会重写父类提供的函数。(默认用自己写的,不用父类提供的)
#include <iostream>
using namespace std;
class Father {
private:
string hobby;
const string &getHobby() const {
return hobby;
}
protected:
string house;
const string &getHouse() const {
return house;
}
public:
string name;
int age;
int money;
void say() const {
cout << "我的名字是" << name << endl;
}
};
class Son : public Father {
public:
void say() const {
cout << "子类-》我的名字是" << name << endl;
}
};
int main() {
Son son;
son.say();
return 0;
}
发现调用的是子类的say函数。
想要调用父类的怎么办?只需要在函数前面标明是父类的。son.Father::say();
#include <iostream>
using namespace std;
class Father {
private:
string hobby;
const string &getHobby() const {
return hobby;
}
protected:
string house;
const string &getHouse() const {
return house;
}
public:
string name;
int age;
int money;
void say() const {
cout << "我的名字是" << name << endl;
}
};
class Son : public Father {
public:
void say() const {
cout << "子类-》我的名字是" << name << endl;
}
};
int main() {
Son son;
son.Father::say();
return 0;
}
5. 多重继承
c++中是能多重继承的,也就是子类可以有多个父类。
格式:class 子类 :封装类型 父类1 , 封装类型 父类2 。比如:class Son : public Father1,public Father2 ;
注意:多重继承,父类中可能出现一样的方法和变量,所以要加前缀区分。
#include <iostream>
using namespace std;
class Father1 {
private:
string hobby;
public:
string name;
int age;
int money;
void say() const {
cout << "我的名字是" << name << endl;
}
};
class Father2 {
private:
string hobby;
public:
string name;
int age;
int money;
void say() const {
cout << "我的名字是" << name << endl;
}
};
class Son : public Father1,public Father2 {
public:
void test() const {
cout << "子类-》我的名字是" << Father1::name << endl;
Father2::say();
}
};
int main() {
Son son;
son.test();
son.Father1::say();
return 0;
}
6. 菱形继承
菱形继承概念:
? 两个派生类A、B继承同一个基类F
? 又有某个类C同时继承者两个派生类A、B。(这时候C就会间接继承两个F。有两个一样的爷爷。因此在调用时,就不知道调用哪个爷爷。)
? 这种继承被称为菱形继承,或者钻石继承。
可以使用 virtual 关键字解决上面产生的问题。
class Animal
{
public:
int m_Age;
};
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 100;
st.Tuo::m_Age = 200;
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
7. 多态
什么是多态?简单来说就是,将一个子类的对象,向上转型为父类。比如 :Father father = Son(); 。这样将Father father作为函数的参数,那么在调用函数时,可以传入各种各样的子类的对象。
多态满足条件
多态使用条件
#include <iostream>
using namespace std;
class Father {
private:
string hobby;
public:
string name;
int age;
int money;
virtual void beat() const {
cout << "我的名字是" << name << endl;
}
};
class Son1 : public Father {
public:
void beat() const override {
cout << "Son1-> 被打" << endl;
}
};
class Son2 : public Father {
public:
void beat() const override {
cout << "Son2-> 被打" << endl;
}
};
void beatSon(const Father& son){
son.beat();
}
int main() {
Son1 son1;
beatSon(son1);
Son2 son2;
beatSon(son2);
return 0;
}
|