本笔记只是记录一些比较简单的随堂随笔,部分内容涉及基础知识的,将会在其他篇文章进行论述。本笔记随时间推移与知识积累不断更新
所有的 C++ 程序都有以下两个基本要素:
程序语句(代码):这是程序中执行动作的部分,它们被称为函数。
程序数据:数据是程序的信息,会受到程序函数的影响。
初步了解–通过输出“Hello World”来看看。
#include
using namespace std;
int main(){
cout << "Hello World"; // 输出 Hello World
return 0;}
using namespace std;可以声明也可以不声明,其本身就是一个对象。但不声明其余代码需要加上std::才能够正常使用。
c++的关键字与c有所同而有所不同,总结如下:
其他的数组,整形变量、字符串等与c相差无几,故不再多说。而c++的cstring这个头文件对字符串的分析相对于c的使用指针、数组对字符串进行分析处理更为快捷方便。部分函数如下图:
示例代码如下:
#include
#include
using namespace std;
int main (){
char str1[11] = "Hello";
char str2[11] = "World";
char str3[11];
int len ; // 复制 str1 到 str3
strcpy( str3, str1);
cout << "strcpy( str3, str1) : " << str3 << endl; // 连接 str1 和 str2
strcat( str1, str2); cout << "strcat( str1, str2): " << str1 << endl; // 连接后,str1 的总长度
len = strlen(str1);
cout << "strlen(str1) : " << len << endl;
return 0;}
引用很容易与指针混淆,它们之间有三个主要的不同:
1. 不存在空引用。引用必须连接到一块合法的内存。
2. 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
3. 引用必须在创建时被初始化。指针可以在任何时间被初始化。
int& r = i;double& s = d;
在这些声明中,& 读作引用。因此,第一个声明可以读作 “r 是一个初始化为 i 的整型引用”,第二个声明可以读作 “s 是一个初始化为 d 的 double 型引用”。
示例代码:
#include
using namespace std;
int main (){
// 声明简单的变量
int i;double d; // 声明引用变量
int& r = i;double& s = d;
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;}
c语言与c++,注释有时是不能重叠,不能一一对应的,所以可以用预处理的方法:if 0与endif进行大规模注释更有优越性。例如引入头文件的时候,为了防止多次导入同个头文件而导致编译错误,可以使用一下的方法注释头文件。
"::“在C++中表示作用域,和所属关系。”::"是运算符中等级最高的,它分为作用域符号(”::“的前面为类名称,后面则是该类的成员名称,C++为例避免不同的类有名称相同的成员而采用作用域的方式进行区分。)、全局作用域符号(当全局变量在局部函数中与其中某个变量重名时可以用::来区分)、::作用域分解运算符(声明了一个类A,且在类A里声明了一个成员函数voidf(),但没有在类的声明里给出f的定义,那么在类外定义f时,就要写成voidA::f(),表示这个f()函数是类A的成员函数。)
与“.”在普通类的中的使用不同的是,“->”是指针的指向运算符,通常与结构体一起使用。例如定义了一个名为stu的指针,然后在主函数用(struct stu s; )定义一个名为s的结构体指针,需要使用时可以直接使用(s->**)来调用。
数据流可分为:cout(输出流)、cin(输入流)、cerr(错误流)、clog(日志流)。具体也难以区别,但最常用的是cout与cin。
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。类与对象的关系就如模具和铸件的关系 类的实例化结果就是对象,而对一类对象的抽象就是类,类描述了一组有相同属性和相同方法的对象。
访问数据类型可分为:公有继承(public)、保护继承(protected)、私有继承(private)。多继承即一个子类可以有多个父类,它继承了多个父类的特性。
C++ 类可以有多个类继承成员,语法如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…{
<派生类类体>};
示例代码:
#include
using namespace std; // 基类
Shapeclass Shape {
public:
void setWidth(int w){
width = w; }
void setHeight(int h){
height = h; }
protected:
int width;
int height;}; // 基类
PaintCostclass PaintCost {
public:
int getCost(int area){
return area * 70; }}; // 派生类
class Rectangle: public Shape, public PaintCost{
public:
int getArea(){
return (width * height); }};
int main(void){
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea(); // 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl; // 输出总花费
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;}
下面介绍下函数的重载:在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
示例代码(温度格式转换器):
#include
using namespace std;
class Student{
public:
void setname(char *name);
void setage(int age);
void setscore(float score);
void show();
private:
char *name;
int age;
float score;
};
void Student::setname(char *name){
this->name = name;}
void Student::setage(int age){
this->age = age;}
void Student::setscore(float score){
this->score = score;}
void Student::show(){
cout<<this->name<<"的年龄是"<<this->age<<",成绩是"<<this->score<<endl;}
int main(){
Student *pstu = new Student;
pstu -> setname("李华");
pstu -> setage(16);
pstu -> setscore(96.5);
pstu -> show();
return 0;
}
this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的。
this 虽然用在类的内部,但是只有在对象被创建以后才会给 this 赋值,并且这个赋值的过程是编译器自动完成的,不需要用户干预,用户也不能显式地给 this 赋值。本例中,this 的值和 pstu 的值是相同的。
联合与枚举:联合是一种特殊的结构体,它的所有成员都分配在同一地址空间上。因此,一个union 实际占用的空间大小与其最大的成员一样,在同一时刻 union 只能保存一个成员的值。使用 union 的目的是让数据更紧密,从而提高程序的性能。但平时最好不用。应用场景:
enum Type {
str, num};
struct Entry {
char *name;
Type t;
char* s; // 如果 t == str,使用 s
int i; // 如果 t == num,使用 i
}
在上述代码中 s 和 i 不可能被同时使用,因此空间浪费掉了。因此可以使用 union 解决上述问题。
union Value {
char* s;
int i;
}
struct Entry {
char* name;
Type t;
Value v; // 如果 t == str,使用 v.s,如果 t == num 使用 v.i
}
枚举类型用于存放用户指定的一组整数值。
一般分两种:
友元类,人称“基佬类”,一个类的所有私有(private)成员和保护(protected)成员都能够与之共享。如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend。示例代码:
#include
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
int main( )
{
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
由于静态方法是属于全体对象共享的,所以并不能使用this指针。this指针是隐藏在类里面的函数中的,如调用函数monitor,同时也将其地址传给了this指针。
静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。
1.静态数据成员在定义或说明时前面加关键字static。//静态变量的定义
2.静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:
<数据类型><类名>::<静态数据成员名>=<值> //静态变量的初始化
这表明:
(1) 初始化在类体外进行,而前面不加static,(这点需要注意)以免与一般静态变量或对象相混淆。
(2) 初始化时不加该成员的访问权限控制符private,public等。
(3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。
3、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。
4、引用静态数据成员时,采用如下格式:
<类名>::<静态成员名> //静态变量的使用方式
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。
当我们使用基类的引用或指针调用基类中定义的某个函数时,我们并不知道该函数真正的对象是什么类型(属于哪个类),因为它可能是一个基类的对象,也可能是一个子类的对象。
虚函数是重载的另一种表现形式。只是一种动态重载的方式,它提供了一种更为灵活的、运行时的多态性机制。虚函数允许函数调用与函数体之间的联系在运行才建立,也就是在运行时才决定如何动作,即所谓动态连编。
示例代码如下:(代码源于
搜狗百科 https://baike.sogou.com/v577043.htm?fromTitle=%E8%99%9A%E5%87%BD%E6%95%B0)
#include
using namespace std;
class Base
{
public:
Base(int _x):x(_x)
{
}
classCrectangle:publicCshape{
public:
virtualvoidDisplay(void){
cout<<"Crectangle"<<endl;}
};
classCtriangle:publicCshape{
virtualvoidDisplay(void){
cout<<"Ctriangle"<<endl;}
};
classCellipse:publicCshape{
public:
virtualvoidDisplay(void){
cout<<"Cellipse"<<endl;}
};
voidmain(){
CshapeobShape;
CellipseobEllipse;
CtriangleobTriangle;
CrectangleobRectangle;
Cshape*pShape[4]={
&obShape,&obEllipse,&obTriangle,&obRectangle};
for(inti=0;i<4;i++)
pShape[i]->Display();
}
使用虚方法结果:
Cshape
Cellipse
Ctriangle
Crectangle
不使用:
Cshape
Cshape
Cshape
Cshape
我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。
#include
#include
using namespace std;
class A{
public:
string m_str;
A(string str):m_str(str){
}
string operator+(const A &text)const;//重载+号运算符
}; //实现重载+号运算符函数
string A::operator+(const A &text)const{
return m_str+text.m_str;
}
int main(){
A a("hello ");
A b("world");
A c=a+b; //两个类相加
cout<<"c="<<c.m_str<<endl;
return 0;}
执行结果:c=hello world
(转自https://mp.weixin.qq.com/s?src=11×tamp=1586000052&ver=2258&signature=BxIhuc5exOZDWlR5BVmGDHo3PGUCWUy-pBZWJKIM3BMm8RBra0njTKd0wCnFRBTSybD86HagarQeILgZEt6ZkeS-Iam2LQN2uJTVpV5uNJOan7DK8bDVXWWyOg9TuY&new=1)
C++能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型。C++也支持重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。重载>><<运算符时,可以把运算符重载函数声明为类的友元函数,这样就可以不用创建对象而直接调用函数。
#include
#include
using namespace std;
class MyClass{
public:
string m_value;
MyClass(stringvalue):m_value(value){
}
friend ostream &operator<<(ostream &output,MyClass &obj);
friend istream &operator>>(istream &input,MyClass &obj);}
//重载输入<<运算符
ostream &operator<<(ostream &output,MyClass &obj)
{
output<<"m_value="<<obj.m_value;
return output;
}
//重载输入>>运算符
istream &operator>>(istream &input,MyClass &obj){
input>>obj.m_value;
return input;
}
int main(){
MyClass obj1("hello world");
cout<<obj1<<endl;
cout<<"请输入一个字符串:"<<endl;
cin>>obj1;
cout<<obj1<<endl;return 0;
}
输出结果:
PS D:\linux-share-dir\c_code> g++ .\app.cpp
PS D:\linux-share-dir\c_code> .\a.exe
m_value=hello world
请输入一个字符串:欢迎学习C++编程
m_value=欢迎学习C++编程
接口描述了类的行为和功能,而不需要完成类的特定实现。C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。
一些接口,例如cout、cin、sort之类的,码农们并不需要确切知道内部的操作,但可以直接拿过来使用。
数据抽象有两个重要的优势:类的内部受到保护,不会因无意的用户级错误导致对象状态受损。类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。如果只在类的私有部分定义数据成员,编写该类的作者就可以随意更改数据。如果实现发生改变,则只需要检查类的代码,看看这个改变会导致哪些影响。如果数据是公有的,则任何直接访问旧表示形式的数据成员的函数都可能受到影响。
示例代码:(基类 Shape 提供了一个接口 getArea(),在两个派生类 Rectangle 和 Triangle 中分别实现了 getArea())
#include
sing namespace std;
// 基类
class Shape
{
public:
// 提供接口框架的纯虚函数
virtual int getArea() = 0;
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生类
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
class Triangle: public Shape
{
public:
int getArea()
{
return (width * height)/2;
}
};
int main(void)
{
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
// 输出对象的面积
cout << "Total Rectangle area: " << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
// 输出对象的面积
cout << "Total Triangle area: " << Tri.getArea() << endl;
return 0;
}
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。C++ 通过创建类来支持封装和数据隐藏(public、protected、private)。我们已经知道,类包含私有成员(private)、保护成员(protected)和公有成员(public)成员。默认情况下,在类中定义的所有项目都是私有的。
class Box{
public:
double getVolume(void) {
return length * breadth * height; }
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
继承的时候只继承一个类的属性,防止出现继承冲突现象。例子如下:
//间接基类A
class A{
protected:
int m_a;};
//直接基类B
class B: public A{
protected:
int m_b;};
//直接基类C
class C: public A{
protected:
int m_c;};
//派生类D
class D: public B, public C{
public:
void seta(int a){
m_a = a; } //命名冲突
void setb(int b){
m_b = b; } //正确
void setc(int c){
m_c = c; } //正确
void setd(int d){
m_d = d; } //正确
private: int m_d;
};
int main(){
D d;
return 0;}
第 25 行代码试图直接访问成员变量 m_a,结果发生了错误,因为类 B 和类 C 中都有成员变量 m_a(从 A 类继承而来),编译器不知道选用哪一个,所以产生了歧义。
为了消除歧义,我们可以在 m_a 的前面指明它具体来自哪个类:
void seta(int a){
B::m_a = a; }
这样表示使用 B 类的 m_a。当然也可以使用 C 类的:
void seta(int a){
C::m_a = a; }
为了更为快捷,就需要请动虚继承这大佬出手了。虚继承(Virtual Inheritance)为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
在继承方式前面加上 virtual 关键字就是虚继承,方法如下:
//间接基类A
class A{
protected:
int m_a;};
//直接基类B
class B: virtual public A{
//虚继承
protected:
int m_b;};
//直接基类C
class C: virtual public A{
//虚继承
protected:
int m_c;};
//派生类D
class D: public B, public C{
public:
void seta(int a){
m_a = a; } //正确
void setb(int b){
m_b = b; } //正确
void setc(int c){
m_c = c; } //正确
void setd(int d){
m_d = d; } //正确
private: int m_d;};
int main(){
D d;
return 0;}
动态数组与c语言的指针数组相似,设置一个指针变量,让其指向数组名(首元素地址),即该指针可代替数组进行数据运算。例如数组a[2]等同于指针a+2*sizeof(int)。创造动态数组,a=new int[5]。释放掉动态数组的空间,delete []a。指针函数和c一样。对的,玩的就是心跳,c没学好能怪谁?
这部分也是老生常谈了,例如int *i=new i这样,分配给一个动态内存给i指针,然后完事了就用delete释放掉内存。
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为零的常量。
#include
using namespace std;
int main ()
{
int *ptr = NULL;
cout << "ptr 的值是 " << ptr ;
return 0;
}
结果是:ptr 的值是0
《C陷阱与缺陷》:NULL表示内存位置0,NULL指针并不指向任何对象。因此除非是用于赋值或比较运算,出于其他任何目的使用NULL指针都是非法的。引用NULL内存内容依编译器的不同而不同。某些C语言实现堆内存位置0强加了硬件级的读保护,在其上工作的程序如果错误使用了NULL指针,将立即终止执行。其他一些C语言实现堆内存位置0只允许读,不允许写。在这种情况下,一个NULL指针似乎指向的是某个字符串,但其内容通常不过是一堆“垃圾信息”。还有些C语言实现对内存位置0既允许读也允许写。在这种实现上面工作的程序如果错误使用了一个NULL指针,则很可能覆盖了操作系统的部分内容,造成彻底的灾难!
编写代码时,我们总是会做出一些假设,断言(assert)就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。
使用assert()至少有几个好处:
#include
#include
int main(void)
{
int a, b, c;
printf("请输入b, c的值:");
scanf("%d %d", &b, &c);
assert(c);//断言
a = b / c;
printf("a = %d", a);
return 0;
}
运行结果会显示标准错误流,在某行某位置。
一共有三种方法调用命名空间
编译器会自动提供一个副本构造器,如果用户编写了副本构造器编译器也不会收回自己提供的副本构造器。
副本构造器在如下情况会被自动调用:
1.当某个函数的返回值的类型是某个类的时候,该函数将创建一个该类对象的副本并把后者返回给自己的调用者。
2.当某个输入参数的类型是某个类的时候,系统将为该输入参数创建一个副本并将其传递到函数里去
3.当用户使用某个对象去初始化另一个对象时。
副本构造器的声明语法:
类名(类名 const &source) 或 类名(类名 const &,给定值的参数列表)
参考链接:https://blog.csdn.net/joan11_3/article/details/51628095
如果头文件位于某个夏季子目录里,那么就使用双引号引起"",并在里面标好路径,如#include"includes/fishc.h"。如果位于某个与当前子目录平行的兄弟目录里,则需要这么写:#include"…/includes/fishc.h"
链接、作用域、存储类相爱相杀,但是又因为观察角度不同而同床异梦。
gcc编译引擎在Linux和Windows上是都可以使用的。在编译很多个源文件的时候,每个源文件都可以称为翻译单元(二进制的.o文件)。翻译单元会链接为一个可执行文件。
每个变量都有一个存储类。以下内容c语言中已经说明过了。
链接这老小子分为三种:外链接(external),内链接(internal)和无链接(none)。
外链接的意思是每个翻译单元都能够访问这个东西。像函数,变量,模板和命名空间一样。
内链接在某个翻译单元里定义的东西只能在翻译单元里使用,在任何函数以外定义的静态变量都有内链接。
无链接是指在函数里面的变量,没有链接。
使用一个template 进行声明,表示T可以引用任意类型的变量。也可以使用template ,效果一样。下面用小甲鱼老师的代码进行解释。
好了,这样就完美地转换了,而且不用使用函数重载,这也是c++的独特魅力之处。
为了明确地表明swap()是一个函数模板,还可以使用swap(i1,i2)语法来调用这个函数。这将明确地告诉编译器它应该使用哪一种类型。
如果某个函数对不同的数据类型将进行不同的处理,就应该对它进行重载。
同样也是,先由个人编写一个类的模板,再由编译器在你第一次使用这个模板时生成实际代码。
例如:
template <class T>
class MyClass
{
MyClass();
void swap(T &a,T &b);
}
//构造器的实现
MyClass<T>::MyClass()由于是类模板,所以要写上<T>,编译器需要码农在这里写出一种MyClass()配合使用的数据类型,必须在尖括号里提供它。因为没有确定的数据类型提供,所以使用T作为占位符。
{
//初始化操作
}
再来个有关栈的:
代码运行效果就是,进去的时候是1,2,3出来是3,2,1。
inline(内联模板),引入内联函数是为了解决程序中函数调用的效率问题。可以将类的对象们都拉进类里面,进行优化。
为了存放大量数据,可以使用容器进行,而不是数组,数组容易受到个数限制。
下面使用向量(vector)为例介绍。
#include
#include
#include
using namespace std;
int main(){
vetor<string>names;
names.push_back("耗子哥");
names.push_back("爱耗子嫂");
for (int i= 0;i < names.size();i++ )
{
cout<<name[i]<<"\n";
}
return 0;
}
在遇到栈这种情况的时候,虽然向量可以使用下标去遍历,但是因为栈是个先进后出的结构,只能使用迭代器(iterator)。
#include
#include
#include
using namespace std;
int main(){
vetor<string>names;
names.push_back("耗子哥");
names.push_back("爱耗子嫂");
vector<string>::iterator iter = names.begin();//调到向量起始位置
while (iter != names.end() )
{
cout<<name[i]<<"\n";
++iter;
}
return 0;
}
#include
#include
#include
#include
using namespace std;
int main(){
vetor<string>names;
names.push_back("AA");
names.push_back("BB");
names.push_back("CC");
names.push_back("DD");
sort(name.begin(), names.end() );
for (int i= 0;i < names.size();i++ )
{
cout<<name[i]<<"\n";
}
return 0;
}