C++21天学会
21天学会C++
标签(空格分隔): c语言
第一章 C++程序组成部分
一. Hello World程序的组成部分
-
C++中的helli-world
#include <iostream> int main(){ std::cout << ”HELLO WORLD“ << std::endl; }
-
预处理器编译指令#include:
预处理器是编译前运行的工具,#include<filename>
让处理器获取指定文件的内容,并将这些代码放在编译指令所处的位置 -
返回值:
很多情况下,一个应用程序被另一个程序启动,而父应用程序想知道子应用程序是否成功完成了任务,程序员可通过使用main的返回值向父应用程序传递成功或错误状态 -
名称空间:
(1)代码中使用std::out而不是cout,原因在于cout位于标准名称空间中(std)。假设调用cout时没有使用空间限定符,且编译器知道cout位于两个地方,那编译器应该调用哪个呢,这显然会让编译器混乱。
(2)代码中频繁添加std限定符很繁琐,为避免添加限定符,可以使用声明using namespaceusing namespace std; cout << "hello world" << endl
(3)using不只可以用于限定命名空间这么大的范围,还可以限定到使用的元素
c using std::cout; using std::endl; cout << "hello world" << endl;
二. 使用typedef替换变量类型
-
typedef关键字
(1)C++允许将变量类型替换为您认为方便的名称
(2)格式为: typedef origionType desTypetypedef unsigned int STRICTLY_OSITIVE_INTEGER STRICTLY_OSITIVE_INTEGER postNumber = 4532
三. C风格字符串与C++风格字符串
-
C风格字符串
(1)C风格的字符串用字符数组来声明,而数组都是需要提前计算好元素个数。
(2)字符数组的元素个事应该为字符串中字符个数+1,因为编译器会在双引号引起的内容后面加上\0char HELLOWORLD2[] = {'h','e','l','l','o','w','o','r','l','d','\0'};
-
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)指针通常声明为指向特定类型,如int,表示指针所示的内存单元存储了一个整数
(2)也可让指针声明为指向一个内存块,这种指针被称为void指针
(3)只声明指针,而不去初始化指针,是给指针赋予了一个垃圾值。因为指针包含的值被视为地址,所以未初始化的指针会导致程序访问非法内存,进而导致程序崩溃int *pInteger = NULL;
-
&符号获取变量地址
所有变量都在内存的一个地址上,可以通过&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 }
-
使用解除引用运算符*访问指针指向的数据
int Age = 30; int* addr = &Age; cout << dec << *addr << endl;
二. 动态内存分配
-
new动态分配内存,delete动态释放内存
(1)使用new来分配新的内存块,需要指定为哪种数据类型分配内存
(2)语法:TypeName* p = new TypeName
,delete 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 */
-
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;
-
当指针变量拷贝到另一个指针变量时,只需对其中一个指针进行delete,多次delete会导致程序崩溃
int* p = new int; *p = 30; int* p1 = p; delete p; delete p1; // 程序崩溃
-
检查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)引用是一个变量的别名,应用初始化时,应让它指向一个变量
(2)语法格式
TypeName original = value
TypeName& ref = original
int original = 30; cout << hex << &original<<endl; // 0x7ffd02a9593c int& ref = original; //引用变量的值是内存上的数据值 cout << hex << &ref << endl; // 0x7ffd02a9593c,引用和original变量的地址相同
- 引用的用处
(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 }
- 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; }
第三章 类
一. 造函数与析构函数
- 构造函数与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(); }
- 构造函数的简写形式
(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(); }
- 析构函数
(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)当一个对象作为函数参数进行传递时,C++的值拷贝只是对象的浅拷贝:只复制其指针成员,不复制指针指向的缓冲区。使得在方法退出时,对象被delete同时也把包含的指针成员指向的缓冲区delete了。当手动delete原函数时,原先的缓冲区已经被delete。会出现错误。
(2)这种错误一般在ms visual stdio模式下会报错 -
复制构造函数
(1)当程序员手动增加了复制构造函数后,编译器在函数调用时,会使用复制构造函数来创建对象,进行对象值传递。
(2)形式:class ClassName{ public: ClassName(const ClassName& origion){ ... } }
-
移动构造函数
(1)因为C++严格按照复制构造函数进行参数传递,在对象的指针成员指向的缓冲区很大时,深复制会造成效率降低
(2)所以在提供复制构造函数的同时,需要提供移动构造函数。形式如下:ClassName(ClassName&& src)
-
构造函数举例
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 */
三. 构造函数与析构函数的其它用途
-
单例类
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 }
-
不允许在栈中创建的类
(1)方法中没有用new产生的类就会在栈里面,要想不让栈中产生类,需要私有化析构函数
。
(2)私有化析构函数后,在函数退出时,不能调用析构方法来销毁对象,所以这样的代码就不能通过编译。class MonsterDB{ private: ~MonsterDB(); // 私有化析构函数 }; int main(){ MonsterDB myDB; // 编译报错,方法中不能再使用类名创建对象 return 0; }
-
this指针
表示当前对象的内存地址 -
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)宏函数通常用于非常简单的计算
(2)宏不考虑数据类型,因此使用宏函数编程很危险using namespace std; #define MAX(a,b) (a>b?a:b) int main(){ cout << MAX(3,8) << endl; // 8 }
-
使用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; }
-
宏函数的优缺点:
(1)优点:宏函数将在编译前就地展开,因此简单宏的性能优于函数调用。因为它避免了创建函数栈,传递参数等高开销cpu
(2)缺点:宏函数不支持任何形式的类型检查。
二. 模板
-
模板声明
(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)模板函数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;
```