虽然C++是大一时学的了,但那时有很多东西一知半解,一直十分惭愧,今天就归纳一下C++的一些用法,算是对之前的交代,也希望能够帮助到大家。
环境是C++98,CLion 2017。C++的语法基本是后相兼容的,故C++11,C++17也没问题。
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;
3 & 5 // 按位与
c = a & 0xff; //相当于取出a的低八位
1||2; //按位或,可用于将变量中某些位置1
1^2;//按位抑或
1<<1;
2>>1;//右移时,符号为不变
隐含转换:由低类型转为高类型。例如:1-true 0-true
浮点赋给整数:小数部分被舍弃,不会四舍五入
显式转换:
下面几种写法完全等价:
int(z) <=> (int)z <=> static_cast<int>(z);
/ 取整
% 取余
typedef double Area, Volume;
typedef int Natrual;
Natrual i1, i2;
Area a;
Volume v;
using area = double; //typedef的等同写法
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 不能够直接给枚举类型赋值,需要强制类型转化;
auto val = val1+val2; //val的类型与val1+val2类型一致
int i;
decltype(i) j=2.123;
cout<<j;
// 2
j与i将会有同样的类型,故为int
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()
是否也是可以的呢?测试了一下,并不行。暂时不知道原因。)
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指向同一块内存空间,浅拷贝。
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
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; //只读指针,值不能改变,也不能赋其他值
inline int add(int a, int b){
return a+b;
}
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;
}
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类中的魔法方法(双下方法),完善了自定义类的操作和功能。