C++理论记录(持续更新)

目录

一、const修饰指针的三种情况

二、派生类的的构造函数必须调用基类

三、字符串数组初始化相关问题

四、指针&数组

五、运算符重载

六、string和char

七、static静态成员的一些性质

八、函数


一、const修饰指针的三种情况

1、const修饰指针 --- 常量指针

const int *p=&a;
也有
int const *p的写法 但基本不用可以忽略

记忆:常量+指针 等于常量指针

修饰的是指针,指针指向可以改,指针指向的值不可以更改
 

2、const修饰常量 --- 指针常量

int *const p=&a;

记忆:*加const 指针常量 要注意一下*的位置

指针的指向不可以改,但指针指向的值可以改

区分以上两者特点的方法:看const 直接修饰的是什么

也就是看const后面跟的东西是什么

对于常量指针 const int *p const后面跟着的是int ,那么就不能对int进行直接操作,也就是不能对于指针指向的值直接操作

对于指针常量 int *const p     const后面直接跟着的是p这个指针,那么就不能直接对于指针进行操作,只能对指针指向的值进行修改。
3、const即修饰指针,又修饰常量

const int *const p=&a;

前两者的特征结合

二、派生类的的构造函数必须调用基类

做实验题的时候看某腾木的代码时发现的问题

class person
{
protected:
	string name;
	int age;
	string sex;
public:	
	person(){}
	person(string n,int a,string x):name(n),age(a),sex(x){}
	void display()
	{
		cout<<name<<" "<<age<<" "<<sex<<endl;	
	}
};


class student:public person
{
protected:
	int regnumber;
	string department;
public:
	student(){}
	student(string n,int a,string x,int r,string d)
	{
		name=n; age=a; sex=x; regnumber=r; department=d;
	}
	void display()
	{
		cout<<name<<" "<<age<<" "<<sex<<" "<<regnumber<<" "<<department<<endl;		
	}
};

会发现一个比较奇怪的现象,基类中写了一个根本没有用的无参构造,然而这却是必需的。

首先一个大前提:在派生类调用构造函数初始化对象的时候,必须调用基类的构造函数。

现在来看下派生类中的构造函数是怎么写的

student(string n,int a,string x,int r,string d)
	{
		name=n; age=a; sex=x; regnumber=r; department=d;
	}

很明显的,这直接在{ }中给所有属性赋了值,而没有使用初始化列表,更别说调用基类的构造函数(这是一种相当图省事的方法)

但基类的构造函数你必须调用,并且这时已经完成了所有属性的赋值,系统不会自动调用基类中编写好的有参构造函数,而是会去寻找无参构造,但很明显,如果你不写无参构造那么基类就没有,就会报错,因此弥补性地写了一个没有任何用处的无参构造函数。

那么平常我们会直接在初始化列表中调用基类的有参构造,这样也不会出现没有调用构造函数的情况,如下:

student(string n,int a,string x,int r,string d):person(n,a,x)
	{
		regnumber=r; department=d;
	}

注:必须在初始化列表而不是在大括号中初始化的成员有:子对象,继承来自基类的数据成员(使用基类的构造函数),常数据成员(const)

同时注意,初始化列表会有两种形式,对于成员直接赋值,对于子对象的构造函数赋值(或者直接调用基类的构造函数,总之就调用基类的有参构造)

class person
{
    int age;
public:
    person(int a):age(a){}
};

class xiaoming:public person
{
    person xiaogang;
    int height;

public:
    xiaoming(int g_a,int m_a,int h):height(h),xiaogang(g_a),person(m_a){}
};

稍微看一下就可以了解,在子类小明中,初始化列表里既有height(h)这种直接将h的值赋给height的操作,也有直接调用继承自基类的构造函数person(m_a)的操作,也有调用子对象的构造函数的操作xiaogang(g_a),关注一下别搞混了

还要注意下顺序问题,从上到下按照顺序初始化的分别是  xiaoming   xiaogang  height
那么在初始化列表中也要按照这个顺序,并且按照逗号表达式从右到左,最先初始化的摆在最右边。

三、字符串数组初始化相关问题

推荐blog:字符串数组初始化相关问题总结 - Tsingke - 博客园

在这里就提一些我自己比较关注的点

1、字符数组初始化给的初值是‘\0’,而整形数组的初始化初值给0,两个在本身的数组中都默认成NULL

2、初始化字符数组写成char arr[10]="HELLO"

可以近似看作char arr[10]={'H','E','L','L','O'}

但仍然有区别,前者我们看作为利用“字符串”赋初值,而后者是一个“字符”一个“字符”的键入。

对于使用字符串初始化而言,数组的长度必须比你键入的长度要大,因为系统会自动在你初始化完了以后在后面添‘\0’,而如果你使用单个字符赋值就不会出现这样的情况

char arr[5]="China"——————ERROR

而char arr[5]={'C','h','i','n','a'}—--RIGHT

所以要注意字符数组的实际分配的空间大小应该是你输入的字符长度+1,毕竟末尾还有一个'\0'

当字符数组的实际输入的元素长度没有定义的长度长时,剩下的所有元素都会被系统自动赋上'\0'

3、对于未知长度的数组,也有计算数组长度的方式(在冒泡排序中我也写过这个问题)

int arr[]={1 ,2,3,5,4,6};

int len =sizeof(arr)/sizeof(int);(或者除以sizeof(arr[0])   )

4、定义数组的场合,数组名可以没有,但数组长度必须有。

省略数组大小只能在  已经有初始化的数组定义之中

5、int array4[5]={ }; //error:语法格式错误

(然而我自己实际在dev中跑一下这个语法是没有问题的,而且每个初始值还是0,似乎将空花括号默认成了里面是0)

6、这些东西虽然不会在实际理论课上考,但我仍然花了时间去搞,因为对于我而言理解真的挺重要的。

整型数组和字符型数组的输入输出所有注意点,都在这了。

整型数组的输入方式必须通过循环来实现,首先 整型数组的语法中无法像字符数组一样提供首地址后能自动完成所有的输入。

char arr0[10]={'\0'};
cin>>arr0;

字符数组为什么默认赋'\0'?是因为作为字符通过ascII转换以后它表示的值也是0,和整型数组其实是一样的,系统会自动将‘\0’作为数值0来处理,所以不管是arr【i】!=0,抑或初始化的时候直接写0,都是可以的。

只通过cin直接想字符数组中输入字符,按下回车的那一刻就会停止,系统会自动在后面加一个\0,因此即便你输入的长度没有预定的长,而且没有实现初始化\0也没有关系,因为通过cout的方式直接输出,系统会自动检索,直至遇到\0,立刻停止。

如果你在cin的过程中按了一个空格  空格后的内容不再记录于数组中而是会变成下一个cin中的内容

(比如你键入123 456  cout的输出流只会输出123           456会到下一个cin中。

利用循环不断重复输入,而在实际输入的时候只需要输入一个字符 就按一下空格 就是这个道理

而整型数组必须一个元素一个元素的输入,而要实现这一点就只能够通过i的循环

int arr[10]={0};
for(int i=0;i<10;i++)
{cin>>arr[i];}

当然 整型数组的输出也要通过循环实现

理解重点:

但需要注意的是,直接cout<<arr的这种方式只有字符数组可以实现,其他的数组cout<<arr,arr会被视为数组的首地址,而不会想象中的自动向前检索。

而这种遇到\0就停的输出方式,也当且仅当你的输出方式是cout<<直接加字符数组首地址

很普通地通过循环来输入输出字符数组中的元素 ,一点问题都没有。(就是输入\0也能给你原封不动的输出\0)

7、还有一个需要注意的点,对于整体数组初始化来说,只有赋0可以做到,其他的都不行,例如:

int a[10]={0};
int b[10]={7};

对于数组a而言,a的每个值都被初始化为0,而对于数组b,只有第一个元素被初始化成了7,其余的都是0,需要注意的是,只要数组完成了初始化,那么即便有的元素没有被赋初值(比如b里面的b[2],b[3]),这些元素也会被默认成0。

但如果只是声明不定义的话,给的还是随机值。

8、姑且先写在这里了,虽然有点偏题。

在基类中声明字符串属性的时候可以只写一个地址。

#include <iostream>
using namespace std;
class person
{
	char *name;
	char sex;
	int age;
public:
	person(char *Name,char Sex,int Age)
	{
		name=Name;
		sex=Sex;
		age=Age;
	}
	void show()
	{
		cout<<"name:"<<name<<endl;
		cout<<"age:"<<age<<endl;
		cout<<"sex:"<<sex<<endl;
	}
};	

关注一下show()函数,直接cout<<name了,乍一看会有点奇怪,怎么输出了一个地址,其实不是。初始化时用char *name的形式,在主函数中char一个字符串,直接把字符串的首地址传过去,直接cout<<数组名就看起来合理多了。

后续代码:

class employee:public person
{
	int basicsalary;
	int leavedays;
public:
	employee(char *n,char s,int a,int salary,int day):person(n,s,a)
	{
		basicsalary=salary;
		leavedays=day;
	}
	void show()
	{
		person::show();
		cout<<"basicSalary:"<<basicsalary<<endl;
		cout<<"leavedays:"<<leavedays<<endl;
	}
};
	
	
	

int main()
{
	char m_name[20]={0};
	char m_sex;
	int m_age;
	int m_salary;
	int m_days;
	cin>>m_name>>m_sex>>m_age>>m_salary>>m_days;
	employee temp(m_name,m_sex,m_age,m_salary,m_days);
	temp.show();
    return 0;
}

四、指针&数组

1、不管什么类型的指针都是4个字节。

2、***一维数组和二维数组作为函数形参

推荐blog:C++中将一维数组和二维数组(动态的和静态的)作为函数的参数进行传递_shuchong159的博客-CSDN博客_c++ 一维数组传参

一维数组传递首地址的时候
形参这样写: void split(char *name)或void split(char name[])
二维数组:void split (char name[][3]) 第二维数组必须给出大小
或者利用指针void split (char **name)   //这个其实已经是动态二维数组的用法了,如果是单纯的静态完全可以不考虑。

3、指针数组和数组指针

推荐blog:数组指针和指针数组的区别 - jack_Meng - 博客园

两者的区分,记得[]的优先级是最高的,如果没有括号优先和前面的数组名结合,那么前面的int*就指明数组的每个元素是指针,因此是指针数组(指针的数组)

int *p[4](*+【】  指针+数组=指针数组)

那如果加了括号,*和数组名优先结合,int(*p)[4]这种写法就等同于int [4] *p

(【】+ *      数组+指针=数组指针)

这样会好理解的多,这个指针指向了一个长度为4的数组,和二维数组的第一维就是一个意思了。

所以在矩阵转换的时候也有这样的写法:
 

int a[3][4]={0};
int (*p)[3];
p=a;

理解一下,对于第一个二维数组,第一层的数组长度为3,第一层的每个元素中存放着长度为4的数组

而对于p而言,p本身对应着一个数组长度为3的空间,刚好和二维数组的第一层相对应,因此会有上面的写法。

4、对于指针数组的初始化

直接指向空NULL变成空指针的初始化当然没什么好说的,需要关注的是实际给出初值是怎么操作的,毕竟你也不知道怎么写地址放进去

char *b[2]={"1234","5678"}

这就是典型的指针数组的初始化方式,将你想让数组存放的成员直接写出来,指针数组中会自动存放字符串“1234”和“5678”的首地址

5、这里再说明我自己在实操写代码一开始没看懂的一个地方:
 

#include <iostream>
#include <string.h> 
using namespace std;
int main()
{
    char *ch[5];
	for(int i=0;i<5;i++) 
	{
		ch[i]=new char[10];
		cin>>ch[i];
	}

 定义了一个指针数组以后,ch中存放的每个元素都是一个指针,而这个指针又指向了在堆区中开辟的一个字符空间,ch[i]就是这个空间的首地址,跟直接char ch[10]一个道理,所以之后直接cin>>ch[i]来完成字符串的输入。

至此,实现字符串数组我们就有了两种方式,简单粗暴的string[n]或者定义一个数组指针char *ch[n]。

6、利用指针创建二维数组

利用指针创建数组对象,说白了就是让指针作为数组名,其实没差,数组名和指针都指向的是数组的首地址,本质上来说是一样的,只不过指针需要通过new空间来创建数组

int *p=new int [n];//创建一个大小为n的数组空间

但要注意的是如果想要单纯通过指针创建二维数组,可以仿照指针数组的写法构造

也可以只用new来构造,如下:

int m,n;
cin>>m>>n;
int **p;//指向指针的指针
p=new int [m];//先让最外层的指针接受一个数组空间
//正常的二维数组是a[3][4]这样,这时候p就已经相当于a[3]了
for(int i;i<m;i++)
{
    p[i]=new int [n];
}
//完成了第二层的构建

五、运算符重载

1、重载左移运算符时,即重载左移运算符<<时(其实>>也一样)返回的是ostream&

在这里返回的是引用的理由是:使每次都能够返回cout本身,从而到达正常的输出流中cout可以炼虚运用左移运算符达到输出目的的效果

就比如:cout<<a<<b;

如果返回的不是& 那很明显b是没有办法输出的。

六、string和char

1、string[n]定义的字符串数组,而不是字符数组

2、用char作for循环的判断条件时要注意判断的属性是' '不是" "(血与泪的教训)

而如果是string定义的字符串数组,判断的属性就是"  "而不是'    '了

七、static静态成员

(1)性质

1、所有对象都共享
2、编译阶段就分配内存
3、类内声明类外初始化
4、可以通过对象访问或者直接通过类名访问

(2)使用

推荐blog:cnblogs.com/zhuzhenwei918/p/8584504.html

这里只谈自己不会的点(期末考试永远的痛)

class person
{
    int age;
    static int totalnumber;
public:
    static int gettotal()
    {
        return totalnumber;
    }
};


int person::totalnumber=0;

类内声明,类外初始化,类外初始化的形式是把int写在最前面(草!) 

类内定义静态函数,静态函数只能访问静态数据成员

静态成员都可以直接通过类名::访问,或者通过对象.访问 

八、函数

1、void 绝对不可以 return 0 , return或者直接不写就可以了

2、对于一个函数来说,只要碰到一个return就直接停止,所以在编译思路中return不一定只是个摆设或者提供返回值的工具,他也可以直接参与到你的逻辑运算

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值