C++21天学会

21天学会C++

标签(空格分隔): c语言


第一章 C++程序组成部分

一. Hello World程序的组成部分

  1. C++中的helli-world

    #include <iostream>
    int main(){
        std::cout << ”HELLO WORLD“ << std::endl;
    }
    
  2. 预处理器编译指令#include:
    预处理器是编译前运行的工具,#include<filename>让处理器获取指定文件的内容,并将这些代码放在编译指令所处的位置

  3. 返回值:
    很多情况下,一个应用程序被另一个程序启动,而父应用程序想知道子应用程序是否成功完成了任务,程序员可通过使用main的返回值向父应用程序传递成功或错误状态

  4. 名称空间:
    (1)代码中使用std::out而不是cout,原因在于cout位于标准名称空间中(std)。假设调用cout时没有使用空间限定符,且编译器知道cout位于两个地方,那编译器应该调用哪个呢,这显然会让编译器混乱。
    (2)代码中频繁添加std限定符很繁琐,为避免添加限定符,可以使用声明using namespace

    using namespace std;
    cout << "hello world" << endl
    

(3)using不只可以用于限定命名空间这么大的范围,还可以限定到使用的元素
c using std::cout; using std::endl; cout << "hello world" << endl;

二. 使用typedef替换变量类型

  1. typedef关键字
    (1)C++允许将变量类型替换为您认为方便的名称
    (2)格式为: typedef origionType desType

    typedef unsigned int STRICTLY_OSITIVE_INTEGER
    STRICTLY_OSITIVE_INTEGER postNumber = 4532
    

三. C风格字符串与C++风格字符串

  1. C风格字符串
    (1)C风格的字符串用字符数组来声明,而数组都是需要提前计算好元素个数。
    (2)字符数组的元素个事应该为字符串中字符个数+1,因为编译器会在双引号引起的内容后面加上\0

    char HELLOWORLD2[] = {'h','e','l','l','o','w','o','r','l','d','\0'};
    
  2. C++风格字符串
    (1)C风格字符串带来的威胁:C语言程序中的strcpy,strcat,strlen等函数,都会寻找终止空字符,如果程序员没有在字符数组末尾添加空字符,这些函数将产生字符数组越界。
    (2)C++提供std::string,这是一种强大而安全的字符串操作方式,他能动态扩展存储大小。

    #include <iostream>
    int main(){
        std::string HELLOWORLD2 = "hello world";
        std::cout << HELLOWORLD2 << std::endl;
    }
    

第二章 指针和引用

C++程序可以在字节和比特级调整应用程序的性能,要编写高质量的程序利用系统资源,理解指针和引用就必不可少。

一. 指针

  1. 声明指针
    (1)指针通常声明为指向特定类型,如int,表示指针所示的内存单元存储了一个整数
    (2)也可让指针声明为指向一个内存块,这种指针被称为void指针
    (3)只声明指针,而不去初始化指针,是给指针赋予了一个垃圾值。因为指针包含的值被视为地址,所以未初始化的指针会导致程序访问非法内存,进而导致程序崩溃

    int *pInteger = NULL;
    
  2. &符号获取变量地址
    所有变量都在内存的一个地址上,可以通过&varialbe来获取变量所处地址。

    #include <iostream>
    int main(){
        int Age = 30;
        int* addr = &Age;
        std::cout << "Integer is at: 0x" << std::hex << &Age << std::endl; // Integer is at: 0x0x7fff1422250c
        std::cout << "Addr is: 0x" << std::hex << addr << std::endl;       // Addr is: 0x0x7fff1422250c
    
    }
    
  3. 使用解除引用运算符*访问指针指向的数据

    int Age = 30;
    int* addr = &Age;
    cout << dec << *addr << endl;
    

二. 动态内存分配

  1. new动态分配内存,delete动态释放内存
    (1)使用new来分配新的内存块,需要指定为哪种数据类型分配内存
    (2)语法:TypeName* p = new TypeNamedelete p
    (3)分配连续内存:TypeName* p = new TypeName[length]delete[] p

    using namespace std;
    int main(){
        cout << "Enter your name:";
        string name;
        cin >> name;
    
        int charAllocate = name.length() + 1;   // 字符串结尾的\0
        char* copyName = new char[charAllocate];
    
        strcpy(copyName,name.c_str());          // 将字符串转换为c类型字符串再拷贝给字符数组
        cout << "Alloacated buffer contains:" << copyName << endl;
        delete[] copyName;
    }
    /**
    Enter your name:lj
    Alloacated buffer contains:lj
    */
    
  2. const关键字用于指针的三种形式
    (1)const TypeName* p:当前指针变量指向的内存地址上的数据为常量,不可修改。但指针指向的地址可以改变。

    int Age=30;
    const int* p = &Age;
    //*p = 40;  // 编译报错,这样已经改变了指针所指向内存的数据
    int num2 = 35;
    p = &num2;  // 改变指针指向的地址可以,编译通过
    cout << dec << *p; 
    

(2)TypeName* const p:指针指向的地址为常量,不可修改,但可修改指向地址上的数据值
c int Age=30; int* const p = &Age; *p = 40; // 改变地址上的值,编译通过 //int num2 = 35; //p = &num2; // 改变指针指向的地址,编译不通过 cout << dec << *p;
(3)const TypeName* const p:指针包含的地址,和改地址指向的值都不能修改
c int Age=30; const int* const p = &Age; // *p = 40; // 不能改变地址上的值 //int num2 = 35; //p = &num2; // 不能改变指针指向的地址 cout << dec << *p;

  1. 当指针变量拷贝到另一个指针变量时,只需对其中一个指针进行delete,多次delete会导致程序崩溃

    int* p = new int;
    *p = 30;
    int* p1 = p;
    delete p;  
    delete p1;  // 程序崩溃
    
  2. 检查new发出的请求分配是否得到满足
    (1)默认情况下,C++在请求分配内存失败的情况下,抛出std::bad_alloc异常

    try{
        int* p = new int[9999999999];
        delete[] p;
    } catch(bad_alloc){
        cout << "Bad allocation" << endl;
    }
    

(2)使用new(nothrow)在内存分配失败的情况下返回NULL
c int* p = new(nothrow) int[9999999999]; if(p) delete[] p; else cout << "allocate fail"<<endl;

三. 引用

  1. 引用是什么
    (1)引用是一个变量的别名,应用初始化时,应让它指向一个变量
    (2)语法格式
    TypeName original = value
    TypeName& ref = original
    int original = 30;
    cout << hex << &original<<endl; // 0x7ffd02a9593c
    
    int& ref = original;  //引用变量的值是内存上的数据值
    cout << hex << &ref << endl;    // 0x7ffd02a9593c,引用和original变量的地址相同
    
  2. 引用的用处
    (1)因为C++函数的参数传递是值拷贝,当参数所占内存很大时,值复制会消耗大量时间。可以把函数的形参设为引用变量,这样在值传递时,引用指向实参地址,不会发生内存拷贝。
    using namespace std;
    
    void square(int& number){
        number = number * number;
    }
    
    int main(){
        int number = 10;
        cin >> number;   // 输入5
    
        square(number);
        cout << number <<endl;  // 25
    
    }
    
  3. const用于引用
    (1)const TypeName& ref = origion:禁止通过引用修改原先变量的值
    (2)把函数形参设置为const引用,既可以避免内存拷贝,又可以避免实参得治在函数体中被修改
    int square(const int& number){
        // number = number * number; 编译错误,禁止通过引用修改变量值
        return number*number;
    }
    
    int main(){
        int number = 10;
        cin >> number;
    
        int result = square(number);
        cout << result <<endl;
    }
    
    

第三章 类

一. 造函数与析构函数

  1. 构造函数与setter,getter
    (1)构造方法写在public代码块内
    (2)Person::name:表示在Person类中生命的name属性。::符号又被称为作用域解析运算符
    using namespace std;
    
    class Person{
    private:
        string name;
        int age;
    public:
        Person(int age,string name){     // 写在public内的构造函数
            this->name = name;
            this->age = age;
        }
    
        const string &getName() const {  // setter与getter
            return name;
        }
    
        void setName(const string &name) {
            Person::name = name;
        }
    
        int getAge() const {
            return age;
        }
    
        void setAge(int age) {
            Person::age = age;
        }
        void introduceInfo(){  // 表示在Person类中生命的name属性。::符号又被称为作用域解析运算符
            cout << "My name is:"<< Person::name << " , age is:"<< Person::age << endl;
        }
    };
    int main(){
        Person* p = new Person(23, "zhangsan");
        p->introduceInfo();
    }
    
  2. 构造函数的简写形式
    (1)使用Person(int gae,string name):name(name),age(age){}的空函数体形式
    class Person{
    private:
        string name;
        int age;
    public:
        Person(int gae,string name):name(name),age(age){}  // 构造函数简写形式
    
        void introduceInfo(){
            cout << "My name is:"<<Person::name<<" , age is:"<<Person::age << endl;
        }
    };
    int main(){
        Person* p = new Person(23, "zhangsan");            // new返回开辟的内存空间地址起始
        p->introduceInfo();
    }
    
  3. 析构函数
    (1)析构函数调用时机:当对象不再在作用域内或被delete删除时,对象被销毁,进而调用析构函数
    (2)析构函数不能重载,每个类只有一个析构函数,如果没有自定义析构函数,编译器会自动加上一个空的析构函数,这样的话,类中动态申请的内存将无法被释放
    class MyString{
    private:
        char* buffer;
    public:
        MyString(const char* input){
            if (input!= NULL){
                this->buffer = new char[strlen(input)+1];
                strcpy(buffer,input);   // 拷贝input到buffer
            }else{
                buffer = NULL;
            }
        }
    
        ~MyString(){
            cout<< "mystring clearn up!" <<endl;
            delete[] this->buffer;
        }
    };
    
    int main(){
        MyString* ms = new MyString("helloworld");
        delete ms;    // 对象被销毁,自动调用析构函数
    }
    

二. 对象的深浅复制

  1. 对象浅复制
    (1)当一个对象作为函数参数进行传递时,C++的值拷贝只是对象的浅拷贝:只复制其指针成员,不复制指针指向的缓冲区。使得在方法退出时,对象被delete同时也把包含的指针成员指向的缓冲区delete了。当手动delete原函数时,原先的缓冲区已经被delete。会出现错误。
    (2)这种错误一般在ms visual stdio模式下会报错

  2. 复制构造函数
    (1)当程序员手动增加了复制构造函数后,编译器在函数调用时,会使用复制构造函数来创建对象,进行对象值传递。
    (2)形式:

    class ClassName{
    public:
        ClassName(const ClassName& origion){ ... }
    }
    
  3. 移动构造函数
    (1)因为C++严格按照复制构造函数进行参数传递,在对象的指针成员指向的缓冲区很大时,深复制会造成效率降低
    (2)所以在提供复制构造函数的同时,需要提供移动构造函数。形式如下:

    ClassName(ClassName&& src)
    
  4. 构造函数举例

    class MyString{
    private:
        char* buffer;
    public:
        // 构造函数
        MyString(const char* input){
            if (input!= NULL){
                this->buffer = new char[strlen(input)+1];
                strcpy(buffer,input);   // 拷贝input到buffer
            }else{
                buffer = NULL;
            }
        }
    
        // 复制构造函数
        MyString(const MyString& origion){
            cout << "copy constructor runing.." << endl;
            if(origion.buffer != NULL){
                buffer = new char[strlen(origion.buffer) + 1];
                strcpy(buffer,origion.buffer);
                cout << "buffer points to:0x" << hex << (unsigned int*)buffer << endl;
            }
            else
                buffer = NULL;
        }
    
        // 移动构造函数
        MyString(MyString&& origion){
            cout << "move constructor running" << endl;
            if(origion.buffer != NULL){
                buffer = origion.buffer;   // 移动后的对象指向原先的缓冲区
                origion.buffer = NULL;     // 对象已移动,原先指针废弃
            }
        }
    
        char* getbuffer(){
            return buffer;
        }
    };
    
    void useMyString(MyString input){   //此时参数传递自调用复制构造函数进行对象生成
        cout << "input is"  << endl;
    }
    
    int main(){
        MyString ms("hello world");
        useMyString(ms);
    }
    /**
        copy constructor runing..
        buffer points to:0x0xe78050
        input is
    */
    

三. 构造函数与析构函数的其它用途

  1. 单例类

    using namespace std;
    
    class President{
    private:
        President(){};    // 私有化所有造方法
        President(const President&); // 私有化复制构造方法
        const President& operator = (const President&);
        string name;
    
    public:
        static President& instant(){        // 暴露一个static的返回引用的方法
            static President onlyInstance;  // 初始化一个静态对象
            return onlyInstance;
        }
        string getName(){
            return name;
        }
        void setName(string inputName){
            name = inputName;
        }
    };
    
    int main(){
        President& onlyInstance = President::instant();
        onlyInstance.setName("lj");
        cout << "President's name:" << onlyInstance.getName() << endl;         // President's name:lj
        cout << "President's name:" << President::instant().getName() << endl; // President's name:lj
    }
    
  2. 不允许在栈中创建的类
    (1)方法中没有用new产生的类就会在栈里面,要想不让栈中产生类,需要私有化析构函数
    (2)私有化析构函数后,在函数退出时,不能调用析构方法来销毁对象,所以这样的代码就不能通过编译。

    class MonsterDB{
    private:
        ~MonsterDB();   // 私有化析构函数
    };
    
    int main(){
        MonsterDB myDB;   // 编译报错,方法中不能再使用类名创建对象
        return 0;
    }
    
  3. this指针
    表示当前对象的内存地址

  4. C++中结构体与类
    (1)关键字struct来自于C语言,在C++编译器看来,它与类相似,差别仅在于struct中的属性默认全部是public的,除非显式指定,否则struct以公有类方式继承。
    (2)代码示例

    struct Human{
        // 构造函数
        Human(const string& inputname,int inputage):name(inputname),age(inputage){};
    private:
        int age;
        string name;
    };
    
    int main(){
        Human fistMan("lj",26);
    }
    

第四章 模板

一. 使用#define定义宏函数

  1. 宏函数定义
    (1)宏函数通常用于非常简单的计算
    (2)宏不考虑数据类型,因此使用宏函数编程很危险

    using namespace std;
    #define MAX(a,b) (a>b?a:b)
    int main(){
        cout << MAX(3,8) << endl;  // 8
    }
    
  2. 使用assert宏函数验证表达式
    (1)引入assert函数先导入assert.h文件

    #include <assert.h>
    int main(){
        char* sayHello = new char[25];
        // sayHello不是空,报错:int main(): Assertion `sayHello == NULL' failed.
        assert(sayHello == NULL);  
        delete[] sayHello;
    }
    
  3. 宏函数的优缺点:
    (1)优点:宏函数将在编译前就地展开,因此简单宏的性能优于函数调用。因为它避免了创建函数栈,传递参数等高开销cpu
    (2)缺点:宏函数不支持任何形式的类型检查。

二. 模板

  1. 模板声明
    (1)模板让程序员可以定义一种适用于不同类型对象的行为
    (2)模板方法声明:

    template <tyoename T1,typename T2>     // template参数声明
    bool fun1(const T1& p1,const T2& p2);
    

(3)模板类声明:
c template <typename T1,typename T2> // template参数声明 class Test{ private: T1 obj1; T2 obj2; public: T1 getObj1(){ return obj1; } };

  1. 模板举例
    (1)模板函数
    using namespace std;
    // 模板函数
    template <typename T>
    const T& getMax(const T& v1,const T& v2){
        if (v1 > v2)
            return v1;
        else
            return v2;
    }
    int main(){
        int v1 = 25;
        int v2 = 35;
        int max = getMax(v1,v2);   // 模板函数调用编译器可自行推断参数类型
        int max2 = getMax<int>(v1,v2); // 模板函数调用也可自行指定参数类型
        cout << max2 << endl;
    }
    

(2)模板类
```c
template
class Person{
private:
T1 name;
T2 age;
public:
Person(const T1& name,const T2& age){
this->name = name;
this->age = age;
}
const T1& getName() const{
return name;
}
const T2& getAge() const{
return age;
}
};

int main(){
    Person <char*,int> p ("zhangsan",23); // 具体化模板类可以手动指定类型
    cout << p.getName() << endl;
    cout << p.getAge() << endl;

    Person <> p1 ("lj",26);  // 由于模板参数中给出了默认类型,所以p1的具体化可以使用空类型
    cout << p1.getName() << endl;
    cout << p1.getAge() << endl;
```
posted @ 2017-06-12 09:53  moon_lord  阅读(1104)  评论(0编辑  收藏  举报