自学内容网 自学内容网

C++初级入门(1)

第一部分 基础语法入门

一、基础

1、变量与常量

1、变量

        变量存在的意义:方便管理内存空间

2、常量

        用于记录程序中不可更改的数据

        #define 常量名 常量值

        const 数据类型 常量名=常量值 ;      

2、数据类型

1、整型

 short        2字节

 int        4字节    

 long        Win4字节,32位linux 4字节,64位linux 8字节

 long long        8字节

2、实型(浮点型)

 float         4字节        7位有效数字

 double         8字节        15-16位有效数组

 默认情况下,输出一位小数,会显示6位有效数字

 科学计数法

         float f1=3e2;        //3*10^2

         float f2=3e-2;        //3*0.1^2

3、字符型

 char         1字节

 字符型并不是把字符本身放在内存中存储,而是将对应的ASCII编码存储

4、字符串

 char 变量名[]="abc"

 string 变量名="abc" //include <string>

5、bool

 1字节

6、数组

 int a[5];

 int b[5]={1,2,3,4,5};

 int c[]={1,2,3};

3、sizeof关键字

统计数据类型所占内存大小

4、案例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void qOne()
{
    int a = 4;
    a += (a++);//9    5+=4 执行a++ 返回4,a递增为5

    a += (++a);//10 5+=5

    //(a++)+= a;
    /*
    a++是一个右值,即不可修改的临时值,+=需要一个左值(即可以被赋值或修改的对象)
    */

    (++a) += (a++);//10
    /*
    ++a a变为5,返回5,++a作为左值
    a++ 当前a为5,返回5,a自增为6
    5+=5
    */
    printf("%d\n", a);
}
void Foo(char str[100])
{                                // 将str被视为char*传递,所以打印的是指针大小
    printf("%d\n", sizeof(str)); // 8 32位操作系统中,指针大小4字节,64位8字节
}
void qTwo()
{

    char str[] = "Hello word";
    char *p = str;
    int n = 10;

    printf("%d\n", sizeof(str)); // 11
    printf("%d\n", sizeof(p));   // 64位8
    printf("%d\n", sizeof(n));   // 4
    // Foo(str);
    void *p1 = malloc(100);
    // sizeof(p1) 返回的是指针本身的大小,而不是 malloc 分配的内存的大小。指针的大小依赖于编译器和操作系统的位数。
    printf("%d\n", sizeof(p1)); // 8
}
struct Test
{
    int Num;
    bool b;
    char sz[10];
};

void qThree()
{
    // 使用了c++的特性new,不能使用gcc编译
    // Test* p=new Test[10];
    struct Test *p = (struct Test *)malloc(10 * sizeof(struct Test));
    Test *p1 = &p[0];
    Test *p2 = &p[8];
    int n = p2 - p1;
    printf("%d", n); // 8
}

void qThreePlus()
{
    // 使用 malloc 分配内存,分配空间足够存放10个 Test 结构体
    void *p = new Test[10];

/*
    void *p1 = (char *)p;                           // 指向第一个 Test 结构体
    void *p2 = (char *)p + 8 * sizeof(struct Test); // 指向第九个 Test 结构体
    printf("sizeof(struct Test):%d\n", sizeof(struct Test));
    // 因为 p1 和 p2 是 void*,我们需要将它们转换为适当的类型才能进行运算
    int n = (char *)p2 - (char *)p1;//128
*/
 /* 
    Test *p1 = (Test *)((char *)p + 0 * sizeof(struct Test)); // 指向第一个 Test 结构体
    Test *p2 = (Test *)((char *)p + 8 * sizeof(struct Test)); // 指向第九个 Test 结构体

    int n = p2 - p1;
 */
    Test *p1 = (Test *)p; // 指向第一个 Test 结构体
    Test *p2 = (Test *)p + 8; // 指向第九个 Test 结构体

    int n = p2 - p1;
    printf("%d\n", n); // 输出 8
}

char *qFour()
{
    char *a = new char[128];
    char *p = a;
    strcpy(p, "aaaa");
    return p;
}
char *qFive()
{
    // 拷贝不了:局部变量,栈上分配内存,函数结束,内存空间会被回收
    // char a[128];
    // 使用malloc或静态数组或者new,生命周期将被延长
    static char a[128];
    // 为什么不能是&a,他是指向包含128字符的数组的指针,不是字符,类型是char (*)[128],不是char*
    char *p = a;
    strcpy(p, "aaaa");
    return p;
}
char *strcpy(char *strDest, const char *strSrc)
{
    char *oStrDest = strDest;
    while (*strSrc != '\0')
    {
        *strDest = *strSrc;
        strSrc++;
        strDest++;
    }
    *strDest = '\0';
    return oStrDest;
}
char *strcpyPlus(char *strDest, const char *strSrc)
{
    int srcLen = strlen(strSrc);
    char *oStrDest = strDest;
    if (strDest < strSrc || strDest - strSrc >= srcLen)
    {
        // 没有内存重叠问题
        while (*strSrc != '\0')
        {
            *strDest = *strSrc;
            strSrc++;
            strDest++;
        }
        *strDest = '\0';
        return oStrDest;
    }
    // 有内存重叠问题
    strDest = strDest + srcLen;
    strSrc = strSrc + srcLen - 1;
    *strDest = '\0';
    for (int i = 0; i < srcLen; i++)
    {
        strDest--;
        *strDest = *strSrc;
        strSrc--;
    }
    return oStrDest;
}

void *memcpy(void *dest, const void *src, int size)
{
    char *cDest = (char *)dest;
    const char *cSrc = (const char *)src;

    for (int i = 0; i < size; i++)
    {
        cDest[i] = cSrc[i];
    }

    return dest;
}
void *memcpyOtherWay(void *dest, const void *src, int size)
{
    char *cDest = (char *)dest;
    const char *cSrc = (const char *)src;

    for (int i = 0; i < size; i++)
    {
        *cDest = *cSrc;
        cDest++;
        cSrc++;
    }
    return dest;
}

二、函数

1、函数的分文件编写

作用:让代码结构更加清晰

1、创建.h头文件

2、创建.cpp源文件

3、在头文件写函数的声明

4、在源文件写函数的定义

//值交换
//1、swap.h
#include <iostream>
using namespace std;

void swap(int a,int b);

//2、swap.cpp
#include "swap.h"

void swap(int a,int b){
    int temp=a;
    a=b;
    b=temp;
    cout << "a=" << a <<endl;
    cout << "b=" << b <<endl;
}

//3、main.cpp
#include <iostream>
#inculde "swap.h"
using namespace std;

int main(){
    swap(1,2);.
    return 0;
}

2、函数默认参数

函数的形参列表是可以有默认值的

#include <iostream>

using namespace std;
int add(int a,int b,int c);
int add(int a,int b=20,int c=30){
    return a+b+c;
}
int main(){
    add(10);//60
    add(10,30);//70

}
/*
1、如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须有默认值int add(int a,int b=20,int c=30)
2、如果函数声明有默认参数,函数的实现就不能有默认参数(声明和实现只能有一个有默认参数)


*/

3、函数占位参数
#include<iostream>
using namespace std;
void func1(int a,int){

}
//占位参数可以有默认参数
void func2(int a,int =20){

}
int main(){
    func1(10,20);

}

4、函数重载 

条件:

        1、同一个作用域

        2、函数名称相同

        3、函数参数类型不同或者个数不同或者顺序不同

        4、返回值不能作为重载条件

#include<iostream>

using namespace std;

//作用让函数名相同,提高复用性
void func1(){

}
void func1(int a){

}
//引用作为重载条件
void func2(int &a){

}
void func2(const int &a){

}

//函数重载碰到默认参数 语法通过调用传入1参数会产生二义性
void func3(int a){

}
void func3(int a,int b=10){

}
int main(){
    int a=10;
    func2(a);//第一个 因为a是可读可写的
    func2(10);//第二个 有const合法

}

三、指针 

 1、指针的定义和使用

1、作用:可以通过指针间接访问内存

        (1)内存编号是从0开始记录的,一般用16进制表示

        (2)可以通过指针变量保存地址

    启动一个程序,系统会给其分配内存空间,内存空间最小1字节,内存中每一个字节都有编号,这个编号称为内存的地址

        

2、定义与使用

#include <iostream>
using namespace std;

int main(){
    //定义指针
    int a=10;
    int* p;
    p=&a;
    
    //指针就是地址
    cout << "a的地址" << &a <<endl;
    cout << "指针p为" << p << endl; //0xABCD
    
    //使用指针
    //可以通过解引用(*)的方式来找到指针指向的内存
    *p=100;
    cout << "a=" << a <<endl;//100
    return 0;
}

2、 指针所占的内存空间

32位操作系统指针占用4字节,64位8个字节

3、空指针与野指针

1、空指针

      指针变量指向内存中编号为0的空间

      用途:用于初始化指针变量

      注意:空指针指向的内存空间不可以访问

      内存编号0~255为系统占用内存,不允许用户访问

#include <iostream>
using namespace std;

int main(){
    //空指针:用于给指针变量初始化,不初始化就不知道指向哪里
    int *p=NULL;
   
    //语法通过,但是不可以访问,0~255之间内存编号不允许访问
    *p=100;
    
    return 0;
}

2、野指针  

        指针变量指向非法的内存空间,不是我们申请的空间

#include <iostream>
using namespace std;

int main(){
    //指针指向内存地址编号为0x1100的空间
    int* p=(int*)0x1100;
    //访问野指针出错
    cout<< *p <<endl;
    return 0;

}

3、万能指针

        不可以定义void类型的变量,因为编译器不知道给变量分配多大的空间,但是可以定义void *类型,因为指针都是4个字节

int main()
{

    int a = 10;
    void *p = (void *)&a;
    // printf("%d\n", *p);//err  p是void*,不知道取几个字节的大小
    printf("%d\n", *(int *)p2); // 10

}

4、const修饰指针

const修饰指针:常量指针

        int a=10;

        const int* p=&a;(const修饰的是int* 所以值不能改)

        指针的指向可以改,但该指针指向的值不可以改

const修饰常量:指针常量

        int a=10;

        int* const p=&a;(const修饰的是p 所以地址不能改)

        const后面跟的p是变量,修饰后成为常量

        指针的指向不能改,但指向的值可以改

const即修饰指针又修饰常量

        const int* const p=&a;

5、指针和数组

int a[5];

数组名a代表数组,也代表第0个元素地址

在数值上:a==&a[0]==&a==地址

若&a[0]=0x0001,则&a[0]+1=0x0005(元素地址+1=跨过一个元素),等同于a+1

&a+1跨过整个数组==0x0021

void array()
{
    int a[5];
    /*
    数组名a代表数组,也代表第0个元素地址
    a==&a[0]==&a==地址
    若&a[0]==01,则&a[0]+1=05(元素地址+1跨过一个元素),等同于a+1
    &a+1跨过整个数组=21

    */
    printf("%d\n", a);
    printf("%d\n", &a);
    printf("%d\n", &a[0]);

    printf("%d\n", &a[0] + 1);
    printf("%d\n", &a[1]);
    printf("%d\n", a + 1);

    printf("%d\n", &a + 1);
    printf("%d\n", &a[4] + 1);
}

int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};

a[0][0]第0行第0个元素

&a[0][0]第0行第0个元素地址  +1跨过一个元素
a[0]第0行一维数组名 +1跨过一个元素
&a[0]第0行地址  +1跨过一行
a二维数组名,首行地址&a[0]  +1跨过一行
&a二维数组地址  +1跨过整个数组

  //元素个数
   int num=sizeof(a)/sizeof(a[0][0]);
   //行数 总大小/一行的大小
   int row=sizeof(a)/sizeof(a[0]);
   //列数 行大小/一个元素大小
   int column=sizeof(a[0])/sizeof(a[0][0]);

6、指针和函数
void swap(int *p1,int *p2){
    int temp=*p1;
    *p1=*p2;
    *p2=temp;
}
int main(){
    int a=10;
    int b=20;
    swap(&a,&b);
}
void bubbleSort(int * arr,int len){//int *arr可以写成int arr[]
    for(int i=0;i<len-1;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;
            }
        }
    }
}
#include <stdio.h>

int add(int a, int b)
{
    return a + b;
}
int subtract(int a, int b)
{
    return a - b;
}

int main()
{
    int num = 10;
    int *pNum = &num;
    // 基本指针类型
    printf("%d\n", *pNum); // 10
    printf("%p\n", &pNum);
    printf("%p\n", pNum); // 保存的num的地址,和&num一样
    printf("%p", &num);
    // 函数指针
    int (*operation)(int, int);
    operation=add;
    printf("a+b=%d\n",operation(5,3));
    

    return 0;
}
/**
 * 指针==地址==编号
 * 指针变量:存放指针(地址)的变量
 * 指针:
 *  基本指针类型
 *      int* char* float* double* void*可以指向任何类型数据,使用前需类型转换
 *  指向指针的指针
 *      int** 指向int*的指针,即指针的指针,用于动态数组等场景
 *  函数指针:指向这个函数
 *      返回值类型 (*函数指针名)(参数类型)
 *  在使用时,对一个表达式取*,就会对表达式减一级*,如果对表达式取&,就会加一级*
 *  32位操作系统中,指针大小4字节,64位8字节
 *
 *
 *
 */

四、结构体

1、定义

struct 类型名称{

        成员列表

};

struct Student {
    int age;
    string name;
}s3;
int main(){
//1、struct Student s1其中struct可以省略
    struct Student s1;
    s1.name="张三";
    s1.age=18;
//2、struct Student s2={...}
    struct Student s2={18,"张三"}
//3、定义时顺便赋值s3
    s3.age=20;
    s3.name="王五";
}

2、结构体数组

struct 数组名[个数]={{},{},{}...}

struct Student{
    string name;
    int age;
};

int main(){
    struct Student stuArr[2]={

        {"张三",18},
        {"王五",20}

    };
    stuArr[1].name="张武";
}

3、结构体指针

->

struct Student{
    string name;
    int age;
};
int main(){
    Student s={"张三",18};
    Student* p=&s;
    p->name="王五";
}

4、结构体嵌套结构体
struct student{
    string name;
    int age;
};
struct teacher{
    string name;
    int age;
    struct student stu;
};
int main(){
    teacher t;
    t.stu.name="小小";


}

5、结构体做函数参数
struct Student {
    int age;
    string name;
};
void printStu1(struct Student s){

}
void printStu2(struct Student *p){
    p->name="李四";
    p->age=32;
}

int main(){
    struct Student s;
    s.name="张三";
    s.age=18;
//值传递
    printStu1(s);
//址传递
    printStu2(&s)
}

如果不想修改主函数中的数据,用值传递,反之用地址传递

6、结构体中const使用

作用:用const防止误操作

struct student{
    string name;
    int age;
};
void printStu(const student * s){


}
int main(){
    student s={"张三",20};
    printStu(&s);
}

值传递会复制副本,所以速度慢

7、案例
struct Student{
    string name;
    int age;
};
struct Teacher{
    string name;
    struct Student arr[5];
};

void allocateSpace(struct Teacher arr[],int len){
    string nameSeed="ABCDE";
    for(int i=0;i<len;i++){
        arr[i].name="Teacher_";
        arr[i].name+=nameSeed[i];

        for(int j=0;j<5;j++){
            arr[i][j].name="Student_";
            arr[i][j].name+=nameSeed[j];
            
            arr[i][j].age=18;
        }
    }
}
int main(){
    Teacher tArr[3];

    int len=sizeof(tArr)/sizeof(tArr[0]);
    allocateSpace(tArr,len);    


}

第二部分 核心编程

一、内存模型

C++程序执行时,将内存大致分为4个区域

 代码区:存放函数体的二进制代码,由操作系统进行管理

 全局区:存放全局变量和静态变量以及常量

 栈区:由编译器进行自动分配与释放,存放函数的参数值,局部变量

 堆区:由程序员分配和释放,若不释放,程序结束由操作系统回收

1、程序运行前 

在程序编译后,生成exe可执行程序,未执行程序前分为两个区域

    代码区

        存放CPU执行的机器指令

        代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

        代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

   全局区

        全局变量静态变量(static)

        包含了常量区:字符串常量和其他常量(const修饰的全局变量)也存放在此

        该区域数据在程序结束后由操作系统释放

  注意:局部变量和局部常量不在全局区

2、 程序运行后

分为栈区和堆区

    栈区

        由编译器自动分配释放,存放函数参数值,局部变量

        注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

   堆区

        new

        delete 

        释放数组delete[] arr;

二、引用

1、引用的基本使用

作用:给变量起别名

语法:数据类型 &别名=原名

2、引用的注意事项

引用必须初始化

初始化后不可改变

int main(){
    int a=10;
    int &b=a;
}
3、引用做函数参数
void swap(int &a,int &b){
//形参中的a是main中a的别名
    int temp=a;
    b=a;
    a=temp;
}
int main(){
    int a=20;
    int b=20;
    swap(a,b);
}

4、引用做函数返回值

不要返回局部变量的引用

函数的调用可以作为左值

int& func(){
    static int a=10;
    return a;
}
int main(){
    int &ref=func();//相当于int* const ref=&a
    ref=20;//内部发现ref为引用,自动转为*ref=20
    test()=1000;
}

5、引用的本质

引用的本质是一个指针常量(指针指向不可变)

6、常量引用

作用:常量引用主要用来修饰形参,防止形参改变实参

void showValue(const int& v){
    //v+=10;
}
int main(){
    int a=10;
    showVlue(a);

    //int& ref=10;引用本身需要一个合法的内存空间,因此错误
    const int& ref=10;//加上const之后,编译器将代码进行修改 int temp=10;const int& ref=temp;
    //ref=20;加入const之后成为只读,不可修改
}

三、类和对象

1、封装
#include<iostream>
#include<string>

using namespace std;

class Student{
public:
    //成员属性/成员变量
    string m_Name;
    int m_Id;


    //成员函数/成员方法
    void show(){
        
    }
    void set(string name,int id){
        this->m_Name=name;
        this->m_Id=id;
    }
};
int main(){
    Student s1;
    s1.m_Name="张三";
    s1.m_Id=1;
    s1.show();
}

权限

        public 公共权限         成员类内可以访问,类外可以访问

        protected 保护权限   成员类内可以访问,类外不可以访问 继承子可以访问父保护内容

        private 私有权限       成员类内可以访问,类外不能访问

 struct和class的区别为:默认访问权限不同

        struct:public

        class:private

2、对象的初始化和清理
2.1 构造与析构

构造函数

        有参构造/无参构造

        普通构造/拷贝构造

析构函数:不可以有参数,不能重载

#include<iostream>

using namespace std;

class Person{
public:
 
    Person(){
    //没有返回值 函数名和类名相同 可以有参数,可以重载  创建对象时,会自动调用一次 默认提供
    
    }
    Person(string name,int age){
        this->name=name;
        thia->age=age;
    }
    //拷贝构造
    Person(const Person &p){
        this->age=p.age;
        this->name=p.name;
    }
    ~Person(){
    //没有返回值 函数名和类名相同 不可以有参数 对象销毁前,会自动调用一次 默认提供

    }


private:
    string name;
    int age;
};
int main(){
    //构造函数调用方式1
    Person p1;//栈上的数据,main执行完毕会释放
    Person p2("li",10);
    Person p3(p2);

    //不会创建对象,编译器会认为是函数的声明
    Person p4();

    //构造函数调用方式2
    Person p5=Person("zhang",30);
    //匿名对象 当程序执行结束后,系统立刻回收(这行代码执行后回收)
    Person("zhang",30);
    //不能利用拷贝构造函数初始化匿名对象,编译器会任务Person(p3) === Person p3; 对象声明重定义

    构造函数调用方式3    隐式调用-拷贝构造
    Person p6=p3;
}
2.2 拷贝构造调用时机 

拷贝构造函数调用时机

        使用一个已经创建完毕的对象来初始化一个新对象

        值传递的方式给函数参数传值

        以值方式返回局部对象 

#include<iostream>

using namespace std;

class Pereson{
public:
    Person (){
        
    }
    Person(int age){
        this->age=age;
    }
    Person(const Person &p){
        this->age=p.age;
    }
    ~Person(){

    }

private:
    int age;
};
void doWork1(Person p){
//值传递的本质会拷贝出一个临时副本出来
}
Person doWork2(){
    Person p1;
    //会根据p1拷贝出一个新的对象返回
    return p1;
}
//拷贝构造函数调用时机
void demo1(){
    //1、使用一个已经创建完毕的对象来初始化一个新对象
    Person p1(10);
    Person p2(p1);


    //2、值传递的方式给函数参数传值
    Person p3;
    doWork1(p3);
    //3、值传递返回局部对象
    Person p4=doWork2();
}
2.3 构造函数调用规则 

默认情况下,C++编译器至少给一个类添加3个函数

        1、默认构造

        2、默认析构

        3、默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则

        如果用户定义有参构造,c++不再提供默认无参构造,但是会提供默认拷贝构造

        如果用户定义拷贝构造函数,c++不会再提供其他构造函数

2.4 深拷贝与浅拷贝 

深拷贝与浅拷贝 

        浅拷贝:简单的赋值拷贝操作

        深拷贝:在堆区重新申请空间,进行拷贝操作 

总结:如果属性有在堆区开辟内存的,一定要提供拷贝构造函数,防止浅拷贝带来的问题

#include<iostream>

using namespace std;

class Person{
public:
    Person(){}
    Person(int age,int height){
        this->age=age;
        //堆区创建的返回就是int*
        this->height=new int(height);
    }

    ~Person(){
        if(height!=NULL){
            delete height;
            height=NULL;
        }

    }
private:
    int age;
    int *height;
};

void demo1(){
    Person p1(18,160);
    //报错 
    Person p2(p1);
/*
原因:创建了p1,p2两个对象
如果利用编译器提供的拷贝构造函数,会做浅拷贝操作
堆区开辟的空间,浅拷贝将地址拷贝
执行析构时p2先被释放,同时释放堆区内存,p1在释放就会出错
浅拷贝的问题就是堆区内存重复释放,使用深拷贝解决

*/

}
#include<iostream>

using namespace std;

class Person{
public:
    Person(){}
    Person(int age,int height){
        this->age=age;
        //堆区创建的返回就是int*
        this->height=new int(height);
    }
    Person(const Person &p){
        //深拷贝
       this->age=age;
       this->height=new int(*p.height); 
    }
    ~Person(){
        if(height!=NULL){
            delete height;
            height=NULL;
        }

    }
private:
    int age;
    int *height;
};

void demo1(){
    Person p1(18,160);

    Person p2(p1);

}
2.5 初始化列表 

初始化列表 

#include <iostream>

using namespace std;

class Person{
public:
    
    //初始化列表初始化
    Person():age(10),height(20){}
    Person(int a,int h):age(a),height(h){}

private:
    int age;
    int height;
};

2.6 类对象作为类成员 

类对象作为类成员

#include<iostream>

using namespace std;

class A{

};

class B{
public:
    A a;
};

void demo1(){

    B b;    
}
//先有A再有B

当其他类对象作为本类成员,构造时候先构建类对象,在构造自身,先析构本身,在析构类对象

2.7 静态成员 

静态成员 

        静态成员就是在成员变量和成员函数前加上关键字static

  静态成员变量

        所有对象共享同一份数据

        在编译阶段分配内存

        类内声明,类外初始化

  静态成员函数

        所有对象共享同一个函数

        静态成员函数只能访问静态成员变量

#include<iostream>

using namespace std;

class A{
public:
    static int m_A;
    static void func(){}
};

int A::m_A=100;
//静态成员变量,不属于某个对象,所有对象共享同一份数据
//因此,两种访问方式:通过对象访问/通过类名访问
void demo1(){
    A a1;
    //a1.m_A=200;
    A a2;
    a2.m_A=200;
    
    //A::m_A=300;
}

3、c++对象模型和this
3.1 成员变量和成员函数

成员变量和成员函数分开存储

#include<iostream>

using namespace std;

/*
变量只能存储在 min(他的长度,pack参数)的整数倍地址上
    char 地址为1的倍数
    short  2的倍数 0 2 4 8
    int    4的倍数 0 4 8 12
    double 8的倍数 0 8 16 24
结构体整体对齐跟他的 min(最长的字段,pack)整数倍对齐
数组按照数组类型来对齐
结构体嵌套结构体按照被嵌套的最大元素长度对齐
*/

struct AA{
    long long a; //8
    char b; //
    int c;  //和12对齐
    char d[2]; //偏移量目前16+2+(6)=8*3

}
struct BB{
    long long a;//8
    char b;//1+7
    struct AA c;//按8*2对齐
    char d[2];//偏移量16+24+2+(6)=8*6
}

/*

#pragma pack(show) 默认16
//16一般超过结构体中最大大小,所以没影响
如果
#pragma pack(2)
那么int地址 0 2 4 6

struct CC{
    long long a; //8
    char b; //2
    int c;  //和10对齐
    char d[2]; //偏移量目前14+2=2*8

}

*/



#include<iostream>


class A{};
class Person{
public:
   int a;//非静态成员变量 属于类的对象 4字节
   static int b; //静态成员变量 不属于类对象
   void func1(){}//非静态成员函数 不属于类的对象
   static void func2(){}//静态成员函数 不属于类的对象

};
int Person::b;

void demo1(){
//空对象占用内存空间sizeof(a) 为1字节
    A a;
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占用内存的位置 每个空对象也应该有一个独一无二的内存地址

}
void demo2(){
    Person p;
    //sizeof(p); 4字节 只有非静态成员变量属于类的对象

}
3.2 this指针 

 this指针

        成员变量和成员函数分开存储

        每个非静态成员函数只会诞生一份函数实例,也就是多个同类型的对象会公用一块代码

        this指针指向被调用的成员函数所属的对象

this指针的用途

        当形参和成员变量同名时,使用this区分

        在类的非静态成员函数中返回对象本身,可使用return *this,this指针指向对象,解引用返回对象本身

#include<iostream>

using namespace std;

class Person{
public:
    //如果返回值写Person 会调用拷贝构造函数返回新的对象 
    Person& PersonAddAge(Person &p){
        this->age+=p.age;
        return *this;
    }

    int age;

};
void demo1(){
    Person p1;
    p1.age=10;
    Person p2;
    p2.age=10;
    p2.PersonAddAge(p1).PersonAddAge(p2);//30
}

空指针可以访问成员函数,但要注意this,空指针不能访问属性

3.3 常函数与常对象 

const修饰成员函数

        成员函数加const成为常函数

        常函数不可以修改成员属性

        成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象

        声明对象前加const称为常对象

        常对象只能调用常函数

#include <iostream>


using namespace std;


class Person{
public:
    //this指针的本质 指针常量 指针的指向不可以改变
    //加const 本质修饰的this指针,让指针指向的值不可以改变
    void show() const{
        //this->a=100;
        this->b=10;
    }

    int a;
    mutable int b;//在常函数中也想修改
};

void demo2(){
    const Person p;//常对象,属性不可以修改,只能调用常函数,不能调用普通函数,因为普通函数可以修改属性
    //p.a=100;
    p.b=100;
}

4、友元

友元:让一个函数或者类,访问另一个类中私有成员

全局函数做友元

   

#include<iostream>

using namespace std;

class Building{
//goodGuy可以访问私有成员了
friend void goodGuy(Building *building);
public:
    Building(){
        this->sittingRoom="客厅";
        this->bedRoom="卧室;"
    }
public:
    string sittingRoom;
private;
    string bedRoom;


};
//全局函数
void goodGuy(Building *building){
    println(building->sittingRoom);
    println(building->bedRoom);
}
void demo1(){
    Building building;
    goodGuy(&building);

}

 类做友元

#include<iostream>

using namespace std;

//声明
class Building;

class Building{
//GoodGuye类可以访问私有成员了
friend class GoodGuy;
public:
    Building();
public:
    string sittingRoom;
private;
    string bedRoom;


};
//类外可以初始化成员函数
Building::Building(){
    this->sittingRoom="客厅";
    this->bedRoom="卧室;"
}


class GoodGuy{
public:
    GoodGuy(){
        building=new Building;
    }
    void visit();    
    Building *building;
};

void GoodGuy::visit(){
    println(building->sittingRoom);
    println(building->bedRoom);
}

void demo1(){
    GoodGuy goodGuy;
    goodGuy.visit();

}

成员函数做友元  

#include<iostream>

using namespace std;



class Building{
//visit方法可以访问私有成员了
friend void GoodGuy::visit();    
public:
    Building();
public:
    string sittingRoom;
private;
    string bedRoom;


};
//类外可以初始化成员函数
Building::Building(){
    this->sittingRoom="客厅";
    this->bedRoom="卧室;"
}


class GoodGuy{
public:
    GoodGuy();
    void visit();    
    Building *building;
};

GoodGuy::GoodGuy(){
    buildint=new Building;
}
void GoodGuy::visit(){
    println(building->sittingRoom);
    println(building->bedRoom);
}

void demo1(){
    GoodGuy goodGuy;
    goodGuy.visit();

}

5、运算符重载

运算符重载:对已有运算符重新进行定义,赋予另一种功能,以适应不同数据类型

加号运算符重载

#include<iostream>

using namespace std;

//1、成员函数重载+
class Person{
public:
    int a;
    int b;
    Person operator+(const Person &p){
        Person temp;
        temp.a=this->a+p.a;
        temp.b=this->b+p.b;
        return temp;
    }
};
//2、全局函数重载+
Person operator+(const Person &p1,const Person &p2){
    Person temp;
    temp.a=p1.a+p2.a;
    temp.b=p1.b+p2.b;
    return temp;
}


void demo1(){
    Person p1;
    p1.a=10;
    p1.b=20;

    Perosn p2;
    p2.a=30;
    p2.b=40;

    Person p3=p1+p2;
    //本质调用 Person p3=p1.operator+(p2)
    //Person p3=operator+(p1,p2)

    //p3.a==40;p3.b==60

}

   

#include <iostream>

using namespace std;

class Person{
friend ostream& opetator<<(ostream &cout,Perosn &p);
private:
    int a ; 
    int b;
};
ostream& opetator<<(ostream &cout,Perosn &p){
    cout<<"a="<<p.a<<" b="<<p.b;
    return cout;
}

     

class MyInteger {

friend ostream& operator<<(ostream& out, MyInteger myint);

public:
MyInteger() {
m_Num = 0;
}
//前置++
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}

//后置++
MyInteger operator++(int) {
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}

private:
int m_Num;
};


ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}


//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}

//后置++ 先返回 再++
void test02() {

MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}
class Person
{
public:

Person(int age)
{
//将年龄数据开辟到堆区
m_Age = new int(age);
}

//重载赋值运算符 
Person& operator=(Person &p)
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//编译器提供的代码是浅拷贝
//m_Age = p.m_Age;

//提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);

//返回自身
return *this;
}


~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}

//年龄的指针
int *m_Age;

};
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};

bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}

bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}

string m_Name;
int m_Age;
};

void test01()
{
//int a = 0;
//int b = 0;

Person a("孙悟空", 18);
Person b("孙悟空", 18);

if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}

if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}

};
void test01()
{
//重载的()操作符 也称为仿函数
MyPrint myFunc;
myFunc("hello world");
}


class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};

void test02()
{
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;

//匿名对象调用  
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

6、继承
6.1 基本语法
class Base{};

class A:public Base{
    

}

class 子类(派生类): 继承方式  父类(基类)

6.2  继承方式

公共继承

保护基础

私有继承

缩小了权限

6.3 继承中的对象模型

父类中所有非静态成员属性都会被子类继承下去

6.4 继承中构造和析构顺序

父类构造--子类构造--子类析构--父类析构

6.5 继承同名成员处理方式

当子类与父类出现同名的成员的成员,如何通过子类对象,访问子类或父类同名数据

        访问子类同名成员 直接访问即可

        访问父类同名成员 需要加作用域

6.6 继承同名静态成员处理方式

静态成员和非静态成员出现同名

        访问子类同名成员 直接访问即可

        访问父类同名成员 需要加作用域

class Base {
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}

static int m_A;
};

int Base::m_A = 100;

class Son : public Base {
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};

int Son::m_A = 200;

//同名成员属性
void test01()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
cout << "Son  下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;

//通过类名访问
cout << "通过类名访问: " << endl;
cout << "Son  下 m_A = " << Son::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}

//同名成员函数
void test02()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
s.func();
s.Base::func();

cout << "通过类名访问: " << endl;
Son::func();
Son::Base::func();
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
Son::Base::func(100);
}

6.7 多继承

class 子类:继承方式 父类1,继承方式 父类2

6.8 菱形继承

菱形继承:

       两个派生类继承同一个基类

        某个类同时继承两个派生类

 cl /d1 reportSingleClassLayout类 file.cpp 

class Animal
{
public:
int m_Age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
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;
}
  • 虚继承可以解决菱形继承问题
  • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义利用
7、多态
7.1 基本概念
#include<iostream>
using namespace std;


class Animal{
public:
//如果想执行让猫说话,那函数地址就不能提前绑定需要在运行期间绑定--地址晚绑定
    void speak(){
    //virtual void speak(){
        println("dongwu");
    }

};
class Cat :public Animal{
public:
    void speak(){
        println("miao");
    }
};
class Dog:public Animal{
public:
    void speak(){
        println("wang");
    }
};
void doSpeak(Animal &animal){
    animal.speak();
}


void demo1(){
    Cat cat;
    doSpeak(cat);
    //执行的时动物说话,因为地址早绑定,在编译阶段就确定了函数地址
    
}

动态多态满足条件

        有继承关系

        子类重写父类虚函数

        父类的指针/引用 指向子类的对象

7.2 多态剖析

写了一个虚函数virtual void speak(),类的内部发生了改变,多了一个虚函数指针(vfptr),指向虚函数表(vftable),表中记录着 虚函数的入口地址,当子类继承了父类,同样会继承父类结构,当子类重写父类虚函数,会将自身虚函数表中虚函数入口地址替换为子类虚函数地址。所以当父类指针或引用指向子类对象时,发生多态

7.3 纯虚函数和抽象类

当类中有了纯虚函数,这个类称为抽象类

        无法实例化对象

        子类必须重写抽象类中纯虚函数,否则也属于抽象类

class Base
{
public:
//纯虚函数
//类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};

class Son :public Base
{
public:
virtual void func() 
{
cout << "func调用" << endl;
};
};

void test01()
{
Base * base = NULL;
//base = new Base; // 错误,抽象类无法实例化对象
base = new Son;
base->func();
delete base;//记得销毁
}

7.4 饮品
#include <iostream>

using namespace std;

class AbstractDrink{
public:
    //煮水
    virtual void Boil()=0;
    //冲泡
    virtual void Brew()=0;
    //倒杯
    virtual void PourInCup()=0;
    //加入佐料
    virtual void PutSomething()=0;
    //制作饮品
    void makeDrink(){
        Boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};
Class Coffee :public AbstractDrink{
    void Boil(){

    }
    void Brew(){

    }
    void PourInCup(){
    
    }
    void PutSomething(){

    }
    

};
void doWork(AbstractDrink *drink){
    drink->makeDrink();
    delete drink;
}
void demo1(){
    doWork(new Coffee);

}

7.5 虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类析构

解决:将父类中析构函数改为虚析构或纯虚析构

#include<iostream>
using namespace std;

class Animal{
public:
    virtual void speak()=0;
    virtual ~Animal(){}
};
class Cat :public Animal{
public:
    Cat(string name){
        this->name=new string(name);
    }
    void speak(){

    }
    string *name;
    ~Cat(){
        if (this->name!=NULL){
            delete this->name;
            this->name=NULL;
        }
    }
};
void demo1(){
    Animal *animal=new Cat("加菲");
    animal->speak();
    //父类指针在析构的时候,不会调用子类中析构,导致子类中如果有堆区数据,造成内存泄漏
    delete animal;
}

1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3. 拥有纯虚析构函数的类也属于抽象类

7.6 电脑组装
#include<iostream>

using namespace std;

class CPU{
public:
  virtual void calculate()=0;  
};
class VideoCard{
public:
  virtual void display()=0;  
};
class Memory{
public:
    virtual void storage()=0;
};
class IntelCpu:public CPU{
    void calculate(){}
};
class IntelVideoCard:public VideoCard{
    void display(){}
};
class IntelMemory:public Memory{
    void storage(){}
};
class Computer{
public:
    Computer(CPU *cpu,VideoCard *vc,Memory *mem){
        this->cpu=cpu;
        this->vc=vc;
        this->mem=mem;
    }
    void doWork(){
        cpu->calculate();
        vc->display();
        mem->storage();
    }
    ~Computer(){
        if(this->cpu!=NULL){
            delete this->CPU;
            this->cpu=NULL;
        }
        if(this->vc!=NULL){
            delete this->vc;
            this->vc=NULL;
        }
        if(this->mem!=NULL){
            delete this->mem;
            this->mem=NULL;
        }

    }
private:
    CPU *cpu;
    VideoCard *vc;
    Memory *mem;
};
void demo1(){
    Computer * com=new Computer(new InterCPU,new InterVideoCard,new InterMemory);
    com->doWork();
    delete com;
}

8、文件操作
8.1 文本文件

头文件<fstream>

文本文件:文本以文本的ASCII码的形式存储

二进制文件:以二进制的形式存储

三大类:

        ofstream:写操作

        ifstream:读操作

        fstream:读写

 文本文件写文件

        1、包含头文件

                #include<fstream>

        2、创建流对象

                ofstream ofs;

        3、打开文件

                ofs.open("路径",打开方式);

        4、写数据

                ofs<<"写入的数据";

        5、关闭文件

                ofs.close();

文件打开方式

打开方式解释
ios::in为读文件而打开文件
ios:out写文件
ios:ate初始位置:文件尾
ios:app追加方式写文件
ios:trunc如果文件存在先删除,在创建
ios::binary二进制方式

注意:文件打开方式可以配合使用,利用|操作符

#include <fstream>

void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);

ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;

ofs.close();
}

文本文件读文件

 

#include<iostream>
using namespace std;

#include<fstream>

void demo1(){
    ifstream ifs;
    ifs.open("test.txt",ios::in);

    if(!ifs.is_open()){
        return;
    }

    //1、字符数组
    char buf[1024]={0}; 
    while(ifs>>buf){
        cout<<buf<<endl;
    }   
    
    //2、字符数组
    char buf[1024]={0}; 
    while(ifs.getline(buf,sizeof(buf))){
        cout<<buf<<endl;
    }
    //3、string
    string buf;
    while(getline(ifs,buf)){
        cout<<buf<<endl;
    }
    //4、char c
    char c;
    while((c=ifs.get())!=EOF){/EOF end of file
        cout<<c;
    }

    ifs.close();

}

8.2 二进制文件

写文件

#include<iostream>
using namespace std;
#include <fstream>

class Person{
public:
    char m_Name[64];
    int m_Age;
};

void demo1(){
    ofstream ofs;
    ofs.open("person.txt",ios::out|ios::binary);

    Person p={"张三",18};
    ofs.write((const char*)&p,sizeof(Person));
    ofs.close();
}

 读文件

#include<iostream>
using namespace std;
#include <fstream>

class Person{
public:
    char m_Name[64];
    int m_Age;
};

void demo1(){
    ifstream ifs;
    ofs.open("person.txt",ios::in|ios::binary);

    if(!ifs.is_open()){
        return; 
    }
    Person p;
    ifs.read((char*)&p,sizeof(Person));
    //p.m_Name p.a_Age

    ofs.close();
}


原文地址:https://blog.csdn.net/m0_64837052/article/details/142897916

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!