C++语言学习笔记

来源:互联网 发布:单晶和多晶冰糖 知乎 编辑:程序博客网 时间:2024/05/01 14:17
  1. C++是有bool类型的,而C没有,只能使用int类型等于1还是等于0来判断
  2. C语言的变量必须定义在函数体的最前面,而C++可以随意定义
  3. C++可以使用<<来连续输出值:cout<<a<<b<<endl;
  4. C++可以以下列方式输出进制内容:
cout<<oct<<x<<endl;//输出8进制cout<<dec<<x<<endl;//输出10进制cout<<hex<<x<<endl;//输出十六进制

5.命名空间跟JAVA的import差不多,我们可以使用namespaceA::x来访问命名空间namespaceA的变量x也可以先声明namespaceA,然后直接访问x。如:

namespace A{    int x;}using namespace A;//值得注意的是,我们必须先定义命名空间A,才能声明,否则出错cout<<x;ccout<<A::x;//也可以通过命名空间访问。

6.引用是指给另一个变量起一个别名,引用必须建立在一个存在的变量基础上。引用的使用分为以下几种情况:
变量的引用int a = 10;int &b = a;//我们修改b的值,a的值也会发生变化。
结构体的引用

typedef struct{     int a;    int b;}Stu;Stu stu1;Stu &stu2 = stu1;stu2.a = 10;//我们将会修改stu1的值。

指针的引用:

int a = 10;int *p = &a;int *&q = p;//这个时候,我们是可以把q当成p*q = 20//我们会修改a的值

方法参数的引用

正常情况下,两个int类型的值交换我们需要这样做:int fun(int *a,int *b){,,,,,}fun(&a,&b);//在这里我们需要取地址传入参数而如果我们使用方法传参的话就简单很多:int fun(int &a,int &b){//这里我们是使用a,b的引用}fun(a,b);

7.const:定义一个不可以变化的变量。它分为一下几种情况:
a.普通变量的常量:
const int a = 5;//我们不能对a重新赋值
b.指针类型的常量:

int x = 3;const int *q = NULL;int const *p = NULL;//跟上者是等价的,指针p不能重新再赋值,但是可以赋予新的引用地址p = &y;//正确的*p = 4;//错误的

c.引用地址的常量

int y = 10;int * const p = NULL;p = &y;//这样是不可以的*p = 5;//这样是可以的c.引用地址以及指针都是常量int x;const int *const p = &x;p = &y;//错误,不能再赋值*p = 5;//错误,不能再赋值

d.引用常量:

int x = 3;
const int &y = x;一个引用常量
x = 10//;这个是可行的
y = 20;//这个是不可行的

e.方法传参数的const
void fun(const int &a,const int &y);//这样我们传参,也不能改变他的值了
特殊情况注意:

const int x = 3;int *y  = &x;*y = 5;//**我们可以通过y来修改x的值,所以存在巨大风险**

总的来说,const和#define的区别在于,#define是编译的时候宏替换,而const是会在执行的时候检查代码的。

8.函数的默认值:我们一般情况下是在声明函数的时候写默认值(定义的时候也可以写,但是可能部分版本编译不过去),而且默认值必须从右边开始写,中间不能空缺一个然后再写:

fun(int i,int j = 10,int k = 15)//这样写是对的//fun(int i;int j = 10;int k)//这样就是错的 

调用的话,就非常简单了:

fun(20)//j k是使用默认值, i = 20fun(20,30,40)//i j k都使用传入参数的值

9.函数的重载:

getMax(int x,int y,int z)getMax(double x,double y)//系统会根据参数和函数自动生成函数名:getMax_int_int_int

10.内联函数:内联函数和正常函数在编译上没有区别,但是在执行的时候,有区别,正常函数在执行的时候,会重新开辟栈,然后执行完毕后把结果返回。但是内联函数则是宏替换,把内联函数的代码复制到调用函数里面去执行。值得注意的是,内联编译是建议性的,不是每个函数声明了最终都是内联的,具体是由编译器决定的(通常情况下,逻辑简单,且重复性调用的是会内联函数的,但是递归调用永远不会是内联函数),如:

inline int max(int a,int b,int c){}int main(){    max(a,b,c);//在这里会进行宏替换,把代码拷贝到这里}

11.内存释放和创建:
C++内存创建使用new和delete,C语言使用malloc和free。在C++中,我们可以使用两种方法的任意一种,但是值得注意的是,我们必须配套使用。下面是使用例子:

int *p  = new int[1000];//if(NULL == P){//我们必须通过这种方式判断是否成功//这里表示我们申请内存失败了}delete []p;//如果我们不加中括号,只会释放第一个内存,后面999个就不会释放了p = NULL;

12.C++对象的实例化分为两种情况:
class TV{
int a;
int b;
…..
}
假设我们有一个类TV,并且在方法中释放
栈中的实例化:
TV tv;//系统帮我们释放内存
tv.a = 5;//直接通过.号来访问,值得注意的是,这里跟JAVA不一样,可以不用new,也不用干别的就直接初始化了
堆中的实例化:
TV *p = new TV();//我们要自己释放
p->a = 5;//直接通过->来访问

13.C++中的string类:
a.赋值:
string s1 = “hello”;
string s2(“world”);
string s3 = s2+s1;
string s4 = “hello”+s2;
string s5 = “hello”+s2+”world”
string s6 = “hello”+”world”//这种情况是
s1 == “hello”;//判断字符串是否等于某个值(这里跟JAVA不一样)
s1.size();//判断字符串的长度
s1[0];//根据索引获取字符串的某个字符
getline(cin,name)//获取键盘输入,并复制给name

13.C++的类定义分为类内定义和类外定义,类内定义
是直接在一个类中定义一个方法,而类外定义(分为:同文件定义和分文件定义)是先声明方法,再定义方法。
a.类内定义:

class Student(){    public:    (inline) void display(){//类内定义,会根据复杂度,选择性的编译为内联函数,记住是选择性的    }}

b.类外定义(同文件定义):

class Student(){    public:    void display();}Student::display(){        cout<<"我在输出"endl;    }

c.类外定义(不同文件定义):

先定义一个:Student.hclass Student{    public:    void display();}然后再创建一个具体的类:student.cpp“#”include "car.h"void student::display();

14.C++的内存分区分为:
栈区:系统会自动帮我们释放.
int x = 0;
int *p = NULL;//两者都是分配在栈区的
堆区:内存需要我们自己释放
int *p = new int[20];//
全局区:储存全局变量以及静态变量
常量区:string str = “hello”;//保存的hello就是存储在常量区
代码区:存储逻辑代码的二进制数据

15.C++的构造函数(就算有参数列表,但因为存在默认参数,不需要传递参数的构造函数,就叫做默认构造函数):

Student.hclass Student{    public:    Student();    //这个是能初始化参数的构造函数,他的执行顺序是先于构造函数代码执行。这种初始化列表的方式只能用于构造函数,初始化列表存在的一个意义在于,他可以对const变量执行初始化    Student(string name = "zcj",int age = 11){        name = "zcj";//这行代码运行起来会报错    }    Student::Student(string name,string age):name=("zcj"),age=(11){//这里我们使用初始化列表来初始化    }    Student(string name = "zcj",int age = 11);//这里是重载,并且可以给其赋默认值,但是值得注意的是,我们不能两个数值都赋默认值,因为在调用的时候,会不确定到底是第一个构造函数还是第二个构造函数被调用    private:    const string name;

}
15.拷贝构造函数在对象赋值,传参,以及初始化的时候会被调用,拷贝构造函数不能重载(值得注意的是,拷贝构造函数所会生成两个对象,并且会有两块不同的内存区域):

class Student{//这个是拷贝构造函数,如果我们不定义这个,会自动生成一个默认的拷贝构造函数public Student(Student const &s){}}Student stu1;//这里会调用构造函数Student stu2 = stu1;//这里会调用拷贝构造函数Student stu3 (stu1);//这里会调用拷贝构造函数void fun(Student stu){//值得注意的是,我们这种方法传递参数,会直接复制生成一个新的对象,修改这个对象并不会影响到原来的值,要想修改原来的值,需要使用指针}fun(stu1);//这里我们也会调用拷贝构造函数

16.析构函数是在一个类被销毁的时候调用,如果没有定义构造函数,系统默认会让每个类都有一个默认的析构函数。析构函数没有参数,也不可以重载:

析构函数的定义:~Student(){//在这里执行释放、 销毁等工作}

17.一个对象的产生以及使用销毁过程过程:
申请内存->初始化列表->构造函数->参与运算->析构函数->释放内存
18.指针和数组:

Student *p = new Student[3];p->name;//访问第一个数组对象的name。p[0].name//使用.号访问name的值p++;访问下一个元素如果我们使用p--,使得p的位置在-1或者数组元素之外的值,我们不能直接使用delete []p;来释放掉内存,而是要先指回到原来的数组对象的位置,我们可以使用p = &(stu[index])来使得数组对象指针指到某个值,如果我们使用delete p(没有中括号),那么只会释放p指针所在的对象,不会释放别的其他的数组对象

19.对象的初始化:
实例化对象A时,如果对象A有对象成员B,那么先执行对象B的构造函数,再执行A的构造函数。
如果对象A中有对象成员B,那么销毁对象A时,先执行对象A的析构函数,再执行B的析构函数。
如果对象A中有对象成员B,对象B没有默认构造函数,那么对象A必须在初始化列表中初始化对象B

20.C++的拷贝函数分为深拷贝和浅拷贝,深拷贝会把指针、对象所指向的值重新复制一份给拷贝对象,他们拥有不同的内存地址,而浅拷贝只会把变量地址复制一份。如果我们不重写,使用系统默认的拷贝函数,系统会自动为我们做浅拷贝操作,如果重写拷贝函数,系统会觉得你会自己处理所有浅拷贝,所以浅拷贝不会自动执行,需要自己去写。值得注意的是,如果我们使用了指针,没有实现深拷贝,在释放的时候,会对同一块内存释放两次,所以会报错。
21.申明一个对象有两种方法:
a.栈内存分配
Student stu;//有默认构造函数的时候,后面可以加括号,也可以不加括号,如果没有默认构造函数,则必须要加上括号,并且写入参数。他会分配内存,并且执行构造函数和类的初始化
Student stu = Student();//后面的可以加括号,也可以不加括号,他会自动初始化类,并且执行构造函数
b.堆内存分配,需要我们自己释放
Student *p = new Student;
Student *p = new Student();//在有默认构造函数的时候,这两种情况是一样的,他们会分配内存,初始化类,并且执行默认构造函数

22.对象中申明对象指针,他占得内存只是对象指针的大小。如果我们去new这个对象指针,他的内存是在堆中的,并不是保存在对象中的。
23.this指针就是所在对象的指针地址,每个方法(包括构造函数)默认是会传入一个this指针用来获取当前对象的值,这样代码就能够正确的执行这个对象的数据操作了,这是编译器自动帮我们做的。我们使用this->访问这个对象内的某个值(为什么要用->符号?因为this是一个指针地址)
23.我们可以使用返回this指针来实现链式编程,但是要注意以下几种情况:
a.Student{//如果我们使用这种方式返回,它的得到的并不是原来Student的地址的值,他会重新复制一个值,然后返回。
Student display(){
return *this;
}
}
b.Student{//我们返回他的引用,这样就不会复制了,会返回跟this同一个值
Student& display(){
return *this;
}
}
c.class Student{
Student* display(){//直接返回这个this指针
return this;
}
}
综合上述说明,我们一定要注意,在传参和返回值的时候,C++是会复制一份这个值的(如果是对象,就会拷贝一份新的临时对象,如果是指针,拷贝的是指针地址),所以我们需要修改本体的值时,一定要用指针或者引用,这个跟我们的两个int值交换是一个道理。

24.const更深入的理解:

const Student student;//常对象,它只能调用常成员函数,否则会报错函数申明的时候使用:    void display() const;函数实现的时候使用:void Student::display() const{}定义常函数的时候,我们是不允许修改值的,因为他等价于void Student::display(const Student *this){this->xxx;//这里是不被允许的}我们可以通过添加const修饰来实现方法重载:void display() const{//修饰重载}void display(){}在调用的时候:const Student stu;//这里调用的是常函数Student stu1;stu1.display()//这里调用的是普通函数

25.常对象引用和常对象指针:

const Student *stu;//不能调用非const的数据和值,也就是不能改变stu的值const Student &stu1;不能调用非const的数据和值,也就是不能改变stu的值Student *const stu2;//我们不能改变stu2指向的地址位置,但是可以改变stu2对象内部的数据。

26.C++一个类继承另一个类后,在初始化子类的时候,会先初始化父类的构造函数和数据,在销毁的时候,会执行子类的析构函数,再执行父类的析构函数。
class Person{

}
class Worker:public Person{
}
27.对于Public,private,protected三者而言,private中的方法和变量不能被子类所继承,而protected中的方法和变量能够被继承但是只能被所在类的成员函数中进行访问,不能够在之外访问,这是这三种访问限定符的区别.
27.C++有三种继承方式:public protected private继承。
28.假设父类和子类有一个相同的方法:x();子类会覆盖父类的x()方法,如果我们要想访问父类中的对象,则要使用soldier.Person::display();。同样,如果我们定义了一个父类名字一致的变量,也可以通过这种访问限定符来访问。注意:如果参数不同,是不能算重载的,系统也会隐藏掉父类的函数,子类只有通过访问符::来访问相关方法。
29.父类和子类如果有一个相同的变量,那么子类直接访问到的值跟父类是没有关系的,它访问的是自己的值。同样,在父类中,通过函数或者别的方式访问这个变量,访问的也是父类的值。这跟JAVA是不一样的。
30.我们可以使用向上转型:
Student stu;
People people = stu;
但值得注意的是,我们student继承people的方式必须是public,因为不这样做,如果我们使用Private或者protected来继承,people能访问的数据,student是无法访问的。所以系统会禁用我们这样赋值。
31.当父类的指针指向堆中子类的对象时Person *p = new Soldier;如果直接在最后释放内存(delete p;p=NULL;)则只会执行父类Person的析构函数~Person而不会执行子类Soldier的析构函数~Soldier,这会造成内存的泄漏,如要避免这种情况就需要使用虚析构函数virtual,但如果我们直接释放子类,则不会出现这种情况。
32.多重继承:一个类有父类,他的父类又有父类。
多继承:一个类又多个父类
class Solider:public Person,public Animal{
Solider():Person(xx):Animal(xx){//我们可以通过这种方式用初始化列表初始化父类的构造函数
}
}

33.什么是菱形继承?假设我们有一个类A,一个类B和C分别继承类A,然后还有一个类D,它继承B和C。那么这个就是一个菱形继承关系。值得注意的是,在菱形继承中,很容易出现类A重复定义的情况(因为A的头文件要在B和C中申明),所以必须使用#ifndef #define和#endif来解决这个问题。

#ifndef PERSON_H//这个值可以随便写,但最好是文件名_后缀的形式#define PERSON_Hclass A{}#endifclass B:public A{}class C:public A{}

34.菱形继承中,B类和C类都包含一个A类(B类和C类都会初始化并析构一个A类),这样会导致内存浪费的情况,所以我们可以使用virtual继承来解决这个问题,让B和C都共享一个A。

class A{public:void display(){}}class B:virtual public A{}class C:virtual public A{}class D:public B,public C{}如果我们不使用虚继承,那么我们在访问A的时候,必须是通过B或者C的限定符D.B::display();来访问A中共有的数据或者方法。如果我们使用虚继承,那么可以直接D.display();来访问这个方法,理由是虚继承只会有一个类A,所以编辑器能够很明白的知道,具体指的是哪一个display();

35.多态是指相同对象收到不同消息或不同对象收到相同消息时产生不同的动作。多态分为动态绑定和静态绑定两种情况。
静态绑定:

class A{public int add(int x){}public int add(int x,int y){return x+y;}}A a;a.add(11);a.add(11,12);//这里的操作就是静态绑定,编译器在编译的时候就能够确定到底是调用哪个函数

动态绑定

class A{
public:
void display(){}
}
class B:public A{
public void display(){}
}
class C:public A{
public void display(){}
}
A *a1 = new B();
A *a2 = new C();
a1.display();//打印的是A类的display
a2.display();//打印的是A类的display
要先解决上述问题,想直接通过a1/a2打印出类B类C的display,则需要做这样的申明:
class A{
virtual void display(){}//这样申明,通过父类的指针,就能够直接访问到B类的display()了
}
36.我们在函数前加virtual让这个函数成为虚函数或者虚析构函数,值得注意的是virtual必须修饰一个类的成员函数,不能修饰静态的,不属于一个类的函数、构造函数等。如果修饰inline函数,则Inline会自动去掉。最关键的是,虚函数这个属性会被子类继承起来。如果一个父类的方法A使用了virtual申明,那么子类会自动加上virtual。
36.当C++对象没有任何数据成员的时候,C++对象至少会有一个内存单元,作为内存地址的占位符。
37.如果一个对象包含虚函数,那么在这个对象地址的前四个内存单元是用来保存虚函数表所在的地址的,并且虚函数表的地址是唯一的,所有来自同一个类的对象共用一个虚函数地址表(所以他们的指针地址也是一样的)。假设我们有一个类A和一个类B,B继承自类A:
class A{
public:
int a;
int b;
virtual void display(){}
}
class B:public A{

}
在A和B中,会有一个虚函数指针地址(他们是不同的),这个指针指向一个函数调用地址列表,值得注意的是,如果B中没有display();B所在的虚函数表会包含一个跟A一样的虚函数调用指针地址。如果我们在B中定义了virtual void display(),那么在B中的虚函数列表中,会有一个跟A中不同的display()调用地址,他指向B中的display()所在的代码地址。
对于虚析构函数,如果我们通过父类的指针调用子类,在释放的时候,会先执行父类的析构函数,然后从子类中去查找虚析构函数,最后再执行之类的析构函数.
38.纯虚函数是只有申明没有实现的虚函数,他在虚函数表里的方法地址值是0,他是无法被调用。
virtual void display() = 0;//纯虚函数的申明。
39.包含纯虚函数的类叫做抽象类,抽象类是不能直接实例化对象的,抽象类的子类也可能是抽象类,但抽象类的子类只有完全实现抽象类中的纯虚函数的时候,才能够实例化对象。
40.只有纯虚函数的类叫做接口类,他不能包含数据成员和非纯虚函数甚至是构造函数和析构函数,所以他没有.cpp文件。
41.type_id返回一个type_info对象的引用,如果我们使用基类的指针获得派生类的数据类型,基类必须带有虚函数。对于type_id来说,只能获取对象的实际类型。例:
typeid(*a) == typeid(A);//判断对象a的类型是不是跟A类相同的,我们不能使用指针来获取他的类型,如果使用的是指针,则会直接打印显示这是一个父类的指针地址。例:
A *a = new B();//A是父类,B是子类
cout<

B *b = dynamic_cast<B*>(a);//假设A是父类,B是子类,a是B的父类对象引用

43.C++的异常处理:

try{}catch(int){//捕获int类型,我们如果不适用这个异常,可以不用带形参,只要写类型就行了,如果需要这个值,需要用引用访问}catch(double){}catch(string& s){//捕获string}catch(A a){//捕获A类或者其子类都可以}catch(...){//捕获所有类型}抛出异常使用:throw xxx;//其中这个xxx可以是任意类型,这根JAVA只能抛出exception类的子类是不一样的

44.我们可以通过友元函数来访问一个类的私有变量成员。友元函数分为两种情况,第一种是全局函数的友元函数,第二种是类函数的友元函数。
全局函数的友元函数:
//我们假设有一个类Time
class Time{
friend void printTime(Time &t);//我们在这里申明全局函数printTime为Time的友元类,去打印私有变量,值得注意的是申明的友元函数必须包含当前类作为参数(推荐使用这个类的引用或者指针,直接用一个对象也可以的,但是性能没有这么好而已)。
private:
int hour;
int minute;
int second;
}
//这里是一个全局函数
void printTime(Time &t){
cout<

//假设我们要重载一个类的-号Student.h文件class Student{friend Student &operator+(const Student &s,const Student &s1);//这里是友元运算符的重载,他调用的方式跟类中成员声明运算符重载最大的区别在于,他没有默认的传入一个Student this指针进去(每一个方法都会传入一个默认的指针),如果我们重载了一个运算符,他的前面不是Student类(当前这个类),我们必须要用友元函数的方式去重载运算符(如我们要输出Student,cout<<Student;那么他的左侧是cout这个类,但是如果我们使用成员运算重载符的方式,第一个值默认是Student this指针,所以是不行的,所以我们必须使用友元函数运算符重载。相反,如果我们的第一个数据必须是类的this指针,那么我们必须使用成员运算符重载,比如说[]运算符重载,他的第一个指针必须是this指针,因为他要访问成员的具体值,所以我们必须要使用成员运算符重载)。当然,我们也可以直接申明一个公共运算符重载函数。public://值得注意的是,我们所有的成员运算符的括号里都默认传入一个Student*,但是友元运算符需要自己传入这个指针    void operator-();//这里实现一元运算符重载,我们也可以使用这样一种方式    Studennt &operator-();,这样我们就能够返回当前这个对象引用的值,实现链式编程。    Student &operator++();//前置++    Student operator++(int);//后置++,我们需要加入一个int参数,以及不能使用&符号,在函数定义的时候,也是完全不一样的    Student operator+(const Student &s);//定义一个二元运算符,实现Student+Student,};Student.cpp文件void Student::operator-() {    cout << "重载了Student的-符号";//这里去实现运算符重载相关操作}Student &operator+(Student &s){    //这里做友元运算符重载的函数体申明,值得注意的是,我们的这个运算符重载是不跟Student类相关的,所以他不用Student::引用符}Student Student::operator++(int){//前置++运算符    Student stu(*this);//我们传入指针,让其复制    this->x++;    this->y++;    return old;//这里我们返回新创建的}-student;//我们调用student的一元运算符,执行重载运算的逻辑。

48.C++中的模板函数跟JAVA中的泛型差不多的,但是值得注意的是,没有使用的函数模板,是没有任何代码的。只有在调用的时候,C++才会动态的生成这个函数的代码。我们也可以使用模板对函数进行重载,但这个重载是要在调用的时候才会发生。下面是函数模板定义的方法(我们做的函数申明叫函数模板,实现的叫模板函数)
template //(这个是函数模板)后面不用加分好,而且函数模板的个数不能为零
void display(T a){//(这个是模板函数)方法必须跟在模板申明后才有效
}
template //typename和class两个关键字的作用是一样的
void display(T a){
}
template

原创粉丝点击