C++笔记01(基础语法、指针、模版、面向对象初步)

虽然C++是大一时学的了,但那时有很多东西一知半解,一直十分惭愧,今天就归纳一下C++的一些用法,算是对之前的交代,也希望能够帮助到大家。
环境是C++98,CLion 2017。C++的语法基本是后相兼容的,故C++11,C++17也没问题。

  1. 逗号运算符
a=3*5,a*4;
// a = 15 
b=(a=3*5, a*4);
//a = 15, b = 60
b = (a = 3*5, a=a*4);
//a = 60, b = 60
b = a = 3*5, a=a*4;
//a = 60 , b = 15

从左到右运算,整个表达式的值是右边的值
2. !->&&->|| 优先级从高代低;&&具有短路特性,左边表达式为假,整个表达式就为假;||也具有短路特性,左边为真整个为真
3. 三目运算符:

a = 1 > 2?100:200;
// a = 200 

1 > 2?100:200整体为布尔类型,表达式2和3类型可以不同,条件表达式最终类型为条件表达式中较高的类型(清华大学C++教材)
4. sizeof运算
可以计算出一个类型的字节数。

sizeof(变量名/表达式)
返回类型占用多少字节。
// int 4 float 4 double 8 char 1;
  1. 位运算
3 & 5 // 按位与
c = a & 0xff; //相当于取出a的低八位
1||2; //按位或,可用于将变量中某些位置1
1^2;//按位抑或
1<<1;
2>>1;//右移时,符号为不变
  1. 隐含转换:由低类型转为高类型。例如:1-true 0-true
    浮点赋给整数:小数部分被舍弃,不会四舍五入

  2. 显式转换:
    下面几种写法完全等价:

int(z) <=> (int)z <=> static_cast<int>(z);
  1. 取整 取余
/ 取整
% 取余
  1. 自定义类型
    使用自定义类型可以增加可读性:
typedef double Area, Volume;
    typedef int Natrual;
    Natrual i1, i2;
    Area a;
    Volume v;
using area = double; //typedef的等同写法
  1. 枚举类型
enum WEEKDAY {SUNDAY, MONDAY, TUESDAY}; //按常量处理
    cout<<SUNDAY;
    cout<<MONDAY;

    //0 1

    enum WEEKDAY2 {SUNDAY_2=7, MONDAY_2, TUESDAY_2};
    cout<<SUNDAY_2;
    cout<<MONDAY_2;

    //7 8

值会叠加,整数不能直接复制给枚举类型(除非转化为枚举类型,见下),枚举类型时整形的子集,可以和int做比较。
转化为枚举类型:

enum WEEKDAY2 {SUNDAY_2=7, MONDAY_2, TUESDAY_2};
    WEEKDAY2 day = SUNDAY_2;
    cout << day;
    // 7
WEEKDAY2 a_day;
int a=2;
a_day = WEEKDAY2(a);
//int 不能够直接给枚举类型赋值,需要强制类型转化;
  1. auto以及decltype的使用
auto val = val1+val2; //val的类型与val1+val2类型一致
int i;
decltype(i) j=2.123;
cout<<j;
// 2

j与i将会有同样的类型,故为int

  1. 形式参数在定义时是不占用内存空间的;若实际参数与形式参数不符,会先行强制转化;
  2. 数组操作:当声明一个数组对象时,其标识符本质上是一个指向该数组的起始地址的指针。数组在内存中空间连续。数组实例化的几种方法,带下标与不带下标,如下所示:
    int a[10]={0, 1, 2,3,4,5,6,7,8,9};  //a[0]-a[9]; 一位数组,元素元素间,地址连续,一个挨着一个,a存放的就是地址,a不能重新赋值。
    int b[10] = {1,2,3,4};
    int c[] = {1,2,3,4,5,6};
    int d[3][4] = {1,2,3,4};//二维数组,一位数组构成的数组 此处可以写12个
    int e[][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; //列出全部初始值,第一维可省略,自动推断为3
    int f[][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; //列出全部初始值才可省略

如果是自定义对象的数组,可实用对象数组来批量实例化数组:

class D2{
public:
    D2(int a){}; //如果实例化需要参数,则D2 d[2]={1,2};不需要参数,则直接D2 d[2];两个参数似乎暂时不行;
    ~D2(){};
void print(){cout<<"haha"<<endl;};
};
int main(){
D2 d[2]={1,2}; //打括号中即为实例化参数,若不需要,可以不加。
};

数组的遍历:我们上面说,数组的变量名本质上是一个指向存放数组的内存首地址的指针,那么遍历数组有两种方法:

//直接遍历
int a[2] = {1,2};
for (int i=0;i<2;i++){
cout<<a[i]<<endl;
}
//指针遍历
int a[2] = {1,2};
int *p = a; //a本质上是一个指针
for (int i=0;i<sizeof(a)/sizeof(int);i++){ //用sizeof判断数组长度
	cout<<*p++<<endl; //*p++指的是指向数组内存空间的指针移动到下一个单元;
}

需要特别说明的是,对于一个自定义对象的遍历,在调用指针时,有所不同:

class D2{
public:
    D2(int a){}; //如果实例化需要参数,则D2 d[2]={1,2};不需要参数,则直接D2 d[2];两个参数似乎暂时不行;
    ~D2(){};
void print(){cout<<"haha"<<endl;};
};
int main(){
    D2 d[2]={1,2};
    D2 *p = d;
    for (int i =0;i<(sizeof(d)/ sizeof(D2));i++){
        p++->print();
    }
}

//haha
//haha

大家发现,上面的指针是不是没有前面的了?这是因为事实上p作为一个指针,指向的是一个D2对象的内存空间,指针通过->运算符访问对象的成员方法,而p,则是对象本身。(那么,*p.print()是否也是可以的呢?测试了一下,并不行。暂时不知道原因。)

  1. 引用类型:可实现参数双向传递
void swap(int &a, int &b){
    int t;
    t = a;
    a = b;
    b = t;
}
int main(){
	int a = 1;
	int b = 2;
	swap(a, b);
	cout<<a<<','<<b<<endl;
	//2,1
}

引用变量的创建(注意区分引用与指针)

int a = 1;
int &ra = a; //相当于创建a的别名,与a指向同一块内存空间,浅拷贝。
  1. 函数的重载
    使用不同的参数表来初始化函数,可以实现函数的重载:(亦称为静态多态)
int add(int a, int b){
	cout<<"int"<<endl;
    return a+b;
}

float add(float a, float b){
	cout<<"float"<<endl;
    return a+b;
}
int main(){
add(1,2);
add((float)1.2, (float)2.3);
return 0;
};

//int float
  1. 指针的使用:指针是C++继承的C的特性之一,可以对内存空间进行操作。定义一个变量的指针:
int i=0;
int *p = &i; //*p在定义时表示定义指针变量,在使用时,表示取该指针指向的地址中的内容。
cout<<*p<<endl;
//0

可以先定义空指针,并将其转化为不同类型的指针:

void *pv;
int *pint = static_cast<int *>(pv);

常量的指针和常量指针:

//常量的指针:
int a;
const int *p1 = &a; //p1是指向常量的指针,相当于只读指针
int b;
p1 = &b; //但p1本身不是常量,可以赋其他值;

//指针常量:
int *const p2 = &a; //只读指针,值不能改变,也不能赋其他值
  1. 内联函数 有时,函数过于简单,我们就可以在函数前面加上inline关键词,建议编译器不要创建函数了,直接把函数的代码放到程序里面进行执行,例如:
inline int add(int a, int b){
    return a+b;
}
  1. 模版函数:可以用于传入和处理多种不同的类型,例如现在要比较abc三个数的大小,他们可能是全是int也可能全是double,于是,我们利用模版函数,只需要写一个函数就可以了:
template <typename T>
T max_3(T x, T y, T z){
  if ((x>y) && (x>z)){
      return x;
  }
  if ((y>x) &&(y>z)){
      return y;
  }
  return z;
};
int main(){
int  a=3,b=4,c=5,m;
    m=max_3(a,b,c);
    cout<<"The  max  value  of  "<<a<<",  "<<b<<"  and  "<<c<<"  is  "<<m<<endl;
    double  d=5.3,e=2.9,f=7.8,n;
    n=max_3(d,e,f);
    cout<<"The  max  value  of  "<<d<<",  "<<e<<"  and  "<<f<<"  is  "<<n<<endl;
    char  c1='b',c2='W',c3='6',c4;
    c4=max_3(c1,c2,c3);
    cout<<"The  max  value  of  '"<<c1<<"',  '"<<c2<<"'  and  '"<<c3<<"'  is  '"<<c4<<"'"<<endl;
    return  0;
}
  1. 面向对象相关
    创建对象:
class object{};

这就是一个简单的对象。
我们可以往里面添加一些方法,分别在不同的时候会被调用,最基本的两个,是构造方法与析构方法:

class object{
public:
object(){}; //构造方法
~object(){}; //析构方法
};

构造方法在对象创建时调用,析构方法在对象销毁时调用。对象的生命周期结束,即被销毁;若手动销毁,则使用delete函数:

class object{
public:
object(){}; //构造方法
~object(){}; //析构方法
};
int main(){
	object o[2];
	delete[] o; //可以销毁对象数组
	object o;
	delete o; //可以销毁对象
};

另外,对象的构造函数也是可以重载的。函数的重载可以通过形式参数表、const申明、参数类型来完成。
如下,有两个构造函数,若传入int,则调用第二个构造方法,不传入就是第一个。

class object{
public:
int a = 0;
object(){}; //构造方法
object(int a){this->a=a;};
~object(){}; //析构方法
};

复制构造函数:是一种特殊的构造函数,当对象作为实际参数被传入函数时,会调用对象的复制构造函数:

class object{
public:
int a = a;
object(){}; //构造方法
~object(){}; //析构方法
object(const object &o){ //构造一个临时无名对象 复制构造方法
        a = o.a;
        cout<<"复制构造"<<endl;
    }
};

复制构造函数在不声明时,系统会分配一个缺省的;若手动实现,则该函数接受一个对象的别名(常量)用于代表将被复制的对象,如上面的const object &o。新生成的对象会获取旧对象的某些属性,在复制构造函方法中写出,例如上例中,为a。
复制构造的例子:

class object{
public:
int a = a;
object(){}; //构造方法
~object(){}; //析构方法
object(const object &o){ //构造一个临时无名对象 复制构造方法
        a = o.a;
        cout<<"复制构造"<<endl;
    }
};
int main(){
object o1;
object o2(o1); //用o1对象复制构造o2;
};

下面讲一讲静态成员变量:
静态成员变量的本质是类中所有对象共同维护的一块空间,例如:

class p{
public:
    static int count; //静态成员变量
    p(){
        count++; //若有复制构造,也要加一
    }
    ~p(){
        count--;
    }
};
int p::count=0; //对实例化的对象个数进行计数;
int main(){
	p p1,p2;
    cout<<p1.count<<endl;
    cout<<p2.count<<endl;
};

// 2
// 2

前向引用申明:当a类要调用b类,b类也要调用a类的时候,把哪个类的定义放在前面,都会导致有一个未定义的类,因此,需要用到前向引用声明。注意,在未详细定义前,不能用该类的细节,只能用该类的符号,不能用其来实例化。

class line; //先声明
.....//这里可以有引用到line的代码;
class line{};//再定义

常成员、常对象、常函数:

class c{
public:
    c(){};
    void print(){cout<<"print"<<endl;};
    void print() const{cout<<"print const"<<endl;}; //const也是区分重载函数的因素
};
int main(){
    c const c1;
    c c2;
    c1.print(); 
    c2.print(); 
    //print const
    //print
}

如上,类c中有两个成员函数,print函数被重载了一次,加入了const,变为常成员函数;在main函数中,实例化对象时加入const,对象变为常对象;常对象只能调用成成员函数;在常成员函数中,不能修改类的属性;常对象只能调用常成员函数。
如果要把一个类作为另一个类的成员,该怎么办呢?答案是使用组合类,如下:

class object(){}
class line{
public:
    line(object o2):o(o2){ //引入组合类
        this->o=o2;
    }
private:
    object o;
};

这样,即可将object类对象o2组合进line类中。
此外,类还有种种重载的特性,限于篇幅,此处就不一一列举了,我会在下一篇C++笔记中详细介绍类的其他重载特性,例如运算符重载、输入输出重载。这些重载类似于python类中的魔法方法(双下方法),完善了自定义类的操作和功能。

你可能感兴趣的:(计算机科学,C++)