还记得在初次接触c++的时候,根本看不见去有关模板的任何东西。现在看上去还好一些,现在就将一些基本的总结下来。
其实,按照c++的学习规律看,应该先介绍函数模板后再介绍class模板,可是因为做题需要,就先介绍这个东西。 函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。
template<typename 类型参数1 , typename 类型参数2 , …> class 类名{
//TODO:
};
一旦定义好了类模板,就可以将类型参数用于类的成员函数了,也就是原来使用 int float char等关键词的地方都可以用类型参数来代替。下面是一个
网站的学习例子 ,假设设计一个类模板,首先先使用一个参数初始化容器,后面使用add(x)
方法将初始化 的参数和add里面的参数求和后再输出,要求这个类可以处理 int long string等.
输入的格式为:
3
string John Doe
int 1 2
float 4.0 1.5
则输出为:
JohnDoe
3
5.5
先看一下主函数是怎么处理的 :
int main () {
int n,i;
cin >> n;
for(i=0;i<n;i++) {
string type;
cin >> type;
if(type=="float") {
double element1,element2;
cin >> element1 >> element2;
AddElements<double> myfloat (element1);
cout << myfloat.add(element2) << endl;
}
else if(type == "int") {
int element1, element2;
cin >> element1 >> element2;
AddElements<int> myint (element1);
cout << myint.add(element2) << endl;
}
else if(type == "string") {
string element1, element2;
cin >> element1 >> element2;
AddElements<string> mystring (element1);
cout << mystring.concatenate(element2) << endl;
}
}
return 0;
}
那么,这个模板应该如何定义?
template <class T>
class AddElements {
private:
T m_x; // 这是成员变量,其类型一定是泛型的
public:
AddElements(T x): m_x(x) {} // 这是构造函数初始化参数的一个方法
// 如果两个参数 Point(T1 x, T2 y): m_x(x), m_y(y){ }
T add(T x) const; // 成员函数的返回值类型 返回和
T concatenate(T x) const;
};
// class的成员函数需要在类外定义,仍需带上模板头
template <class T>
T AddElements<T>::add(T sec) const {
return sec + this->m_x;
}
template <class T>
T AddElements<T>::concatenate(T str) const {
return this->m_x + str;
}
这个问题的难点在于如何找到第一次定义时的参数?幸好我会前理解了下this指针,
而且一下子就命中。这里作为新手有几个部分需要注意一下:
定义成员函数的形式,别忘了<T>
与函数模板不同的是,类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型。 这也是一个参数的。
这里需要再澄清一次,你的参数一定是传递给class的成员变量处理,这也就是为什么必须 模板参数。下面的例子就是打印一个数组:很简单,但是很直接。这是无参数的
template <class T>
class Array{
private:
T *ptr;
int size;
public:
Array(T arr[], int s);
void print();
};
template <class T>
Array<T>::Array(T arr[], int s){
ptr = new T[s]; // ptr 是class的私有变量,这里可以直接使用
size = s;
for(int i = 0; i < size; i++){
ptr[i] = arr[i];
}
}
template <class T>
void Array<T>::print(){
for (int i = 0; i < size; i++){
cout << " " << *(ptr + i);
}
cout << endl;
}
int main()
{
int arr[6] = {1,2,4,6,7,4};
Array<int> a(arr, 6);
a.print();
return 0;
}
template <class T, class U>
class testClass {
private:
T x;
U y;
public:
testClass(){
cout << "Constructor called" << endl;
}
};
int main()
{
testClass<char, char> a;
testClass<int , char> b;
}
如果想使用默认参数,也是在声明对象时,能不能设置一个默认的参数?
可以的,只需在声明模板的时候指定就可以了。比如, template<class T, Class U = char>,
在使用的时候可以testClass<char, > a就可以的。
其实与class 模板是一致的,只不过这里处理的是函数,class 在处理上稍微更复杂一些。 还是上代码:
template <typename T>
T myMax(T x, T y){
return x > y ? x : y;
}
int main()
{
cout << myMax<int>(3, 7) << endl;
cout << myMax<char>('a', 'b') << endl;
}
这句是对函数进行模板化的。
相似的事情,函数模板是多个函数做相同的操作。c++ 的特点: 抽象、封装、继承、多态。针对这四个特性,C++引入一系列的特征。
初学者一般说成员函数,后面熟悉了要纠正为方法(method)
explicit的作用是用来声明类构造函数是显示调用的,而非隐式调用,所以只用于修饰单参构造函数。因为无参构造函数和多参构造函数本身就是显示调用的。
以我目前的理解,显式构造就是使用函数(),当然,这个函数是类的构造函数,而且还是单参的,
#include <iostream>
#include <string>
#include <iostream>
class Explicit
{
private:
public:
Explicit(int size){ // 可以使用explicit关键词进行修饰
// 格式: 如 explicit Explicit(int size)
std::cout << "the size is " << size << std::endl;
}
Explicit(const char *str){ // 也可以使用explicit 关键字修饰
std::string _str = str;
std::cout << "the str is " << _str << std::endl;
}
Explicit(const Explicit& ins){
std::cout <<"the Explicit is ins" << std::endl;
}
Explicit(int a, int b){
std::cout << "the a,b is " << a << b << std::endl;
}
};
int main()
{
Explicit test0(15);
Explicit test1 = 10;// hidden call or implicit conversions
Explicit test2 = ("hello,yubo");
Explicit test3 = "hello,yubo";// implicit conversions
Explicit test4 = (1, 10);
Explicit test5 = test1;
}
如果上面类中的两个构造函数使用explicit修饰,像主函数中用=赋值就不会被允许,报错如下:
explicit.cpp: In function ‘int main()’:
explicit.cpp:33:19: error: conversion from ‘int’ to non-scalar type ‘Explicit’ requested
Explicit test1 = 10;// hidden call or implicit conversions
^~
explicit.cpp:35:32: error: conversion from ‘const char [11]’ to non-scalar type ‘Explicit’ requested
Explicit test2 = ("hello,yubo");
^
explicit.cpp:36:19: error: conversion from ‘const char [11]’ to non-scalar type ‘Explicit’ requested
Explicit test3 = "hello,yubo";// implicit conversions
^~~~~~~~~~~~
explicit.cpp:38:21: error: conversion from ‘int’ to non-scalar type ‘Explicit’ requested
Explicit test4 = (1, 10);
~~^~~~~
这是c++的一个class特性,来看一个基本的代码:
class BaseClass{
public:
void disp(){
cout << "Function of Parent Class\n";
}
};
class DerivedClass: public BaseClass {
public:
void disp(){
cout << "Function of Child class\n";
}
};
int main(){
DerivedClass obj = DerivedClass();
obj.disp();
return 0;
}
请注意在实例化时,我们使用的那个类就是调用哪个类,在这个例子中,打印的是,Function of child.如果
,使用BaseClass obj则会打印父类的成员函数。
这里既然提到函数重载(Function overriding),就多说一下吧。函数重载说简单些基本就是让类中的方法在不同的 情况下可以自己去处理一些问题。比如,如果我们的方法所处理的事务处理的参数可能不一致怎么办?这个时候我们 就函数重载了。以下面为例:
class Sum{
public:
int add(int a, int b){
return a + b;
}
int add(int a, int b, int c){
return a + b + c;
}
};
我们可以使用下面的语句进行调用:
cout << obj.add(1,2) << obj.add(1,2,3) << endl;
对操作符进行操作也是类似的。
按照书上的理解,this指针保存了目前 object 的地址,通过这个地址,你可以访问很多普通手段无法访问的数据。
class Demo {
private:
int num;
char ch;
public:
void setMyValue(int num, char ch){
this->num = num;// this is number variable
this->ch = ch; //so, we have to use this
}
void displayMyValues(){
cout << num << endl;
cout << ch << endl;
}
};
int main(){
Demo obj;
obj.setMyValue(100, 'A');
obj.displayMyValues();
return 0;
}
这个例子就很好的解释了this指针在访问成员变量的方法。
这是我第一次看到类似的说法,而且还是很有意思的。这个方法也就是在创建实例之后,我们可以 连续调用其中的成员函数.那么,为什么会使用这个方案,现在还不得而知。
class Demo {
private:
int num;
char ch;
public:
Demo &setNum(int num){ // Here, function prototype
this->num = num; // class name and "&"
return *this;
}
Demo &setCh(char ch){
this->num++;
this->ch = ch;
return *this;
}
void disPlay(){
cout << num << endl;
cout << ch << endl;
}
};
int main()
{
Demo obj;
//obj.setNum(100).setCh('A');
obj.setCh('B').setNum(99);
obj.disPlay();
return 0;
}
在时候这个方案的时候,我们可以清晰的看到,在class中是如何声明的,尤其注意的是,Demo &f().还有一点就是调用函数的顺序没有特别的要求,谁也可以在前面,谁也可以在后面。
2020’s new year is very hard for China.It will always be keep in my mind and contribute my best effort to suort work.
在读书期间,由于自己的研究项目是xdp,该项目必须使用llvm产生bpf架构的代码,所以,也算初步接触了llvm.来到新 的单位,llvm的使用的机会大大增加,所以,必须在最短的时间内能够切入进llvm的开发。
llvm是c++编写的,所以还得夯实自己的c++基础,不管怎样,需要什么就去学习什么。
cmu.edu 这篇ppt介绍了llvm的最基本的语法、规则,需要总结。
ibm工程师的一个实践课程ibm
根据这篇article
int sum(int a, int b) {
return a+b;
}
上面代码代码保存为sum.c
clang sum.c -emit-llvm -c -o sum.bc
clang sum.c -emit-llvm -S -c -o sum.ll
llvm-as汇编代码产生二进制(bitcode)
llvm-as sum.ll -o sum.bc
llvm-dis
llvm-dis sum.bc -o sum.ll
下面以汇编代码进行简单的分析:
; ModuleID = 'sum.c'
source_filename = "sum.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
llvm所处理的IR最高层的类为Module。上面我们可以知道的另外的信息是该计算机的架构为小端(e),
如果为大端的话,则为大写字母E,
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @sum(i32, i32) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
store i32 %0, i32* %3, align 4
store i32 %1, i32* %4, align 4
%5 = load i32, i32* %3, align 4
%6 = load i32, i32* %4, align 4
%7 = add nsw i32 %5, %6
ret i32 %7
}
define dso_local i32 @sum(i32, i32) #0 {
这条语句返回一个类型为i32的值,两个i32的参数,局部变量需要 %符号作为前缀,全局变量使用@作为前缀。
下面几个也很重要。
任意大小的整数以iN的形式(i32, i64, i128)。
浮点类型(32-bit单精度float和64-bit双精度double)
数组以 x >的形式。
Android是一个特别大的软件项目,我们只有在git的管理基础上再进行一次封装。
在Ubuntu上安装repo一般有两种方案,一种是源码,不过这种方案我没有成功;第二种方案是直接通过命令apt install repo即可。
link
link
在我的工作场景下是使用这么一个命令的。
repo init -u ssh://[email protected]:29418/platform/manifest -b zhimo-mr1-dev --repo-url=ssh://[email protected]:29418/tools/git-repo --reference=/home/local_mirror
这里有很多重点,需要简单地记录下。其实,一个简单的repo是这样的:
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest
请不要在意URL,这玩意一通百通。如果需要某个特定的 Android 版本列表:
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-4.0.1_r1
因为我们的开发分支是xx,所以不太一样,这一点需要注意。 初始化成功的界面如下:
repo has been initialized in /home/user/src
在上面的第一条命令中,需要关注的就是-b和--reference选项,后者是指向了一个本地仓库,这样就能大大减轻服务器的压力,这个参数的作用还可以体现在下载内核源代码时。
和直接从Gerrit服务器下载相比优点如下:
代码下载时间大大缩短,基本上就是直接checkout了。
节省虚拟机磁盘空间,因为git相关的二进制文件都链接到镜像中了
降低Gerrit负载
可以用 -m 参数来选择获取 repository 中的某一个特定的 manifest 文件,如果不具体指定,那么表示为默认的 namifest 文件 (default.xml)
repo init -u git://android.git.kernel.org/platform/manifest.git -m dalvik-plus.xml
假设,我的分支是别人已经更改过的,那么,我想使用原生的安卓源代码,怎么办?加个参数:
-m aosp_base/android-10.0.0_r20.xml
这里有一个问题,比如我clone aosp代码时指定的是 -b r20,但是,当在开发途中,突然告知要
切换到r25版本(这里的r25只是一个分支名字,实际中可以叫任何一个名字)。那么,我就删库从头下
吗?事实上是不必的,在aosp的root目录下,重新指定分支即可.
repo init -u ssh://[email protected]:29418/platform/manifest -b zhimo-mr1-dev --reference=/home/local_mirror
这里是不会覆盖的,而且,在我的测试的时候,在一个分支上重新下载分支,同步时还会默认使用之前的标志,比如,自动更新:
repo sync -cdj4 --no-tags
当所有的项目切换到新的分支上,这个sync就已经完成了.但是,如果你在本地的代码上有一些改动,则会提示:
Fetching projects: 100% (747/747), done.
art/: discarding 1 commits
bionic/: discarding 1 commits
build/blueprint/: discarding 1 commits
build/make/: discarding 1 commits
build/soong/: discarding 3 commits
你肯定不希望自己辛辛苦苦修改的代码完全丢弃了啊,那么如何更新呢?
那么,如何完成代码的同步更新?
一个标准的命令是:
repo sync
我们用的命令是:
repo sync -cdj4 --no-tags
这样就可以不必下载过多的tag
江湖救急,将git push这个命令放在这里以备不时之需。
git push origin HEAD:refs/for/git-repo-branch
在aosp中,进入到每个repo中,前期的操作与正常的git操作是一模一样的,只是在提交 commit的时候,使用上面的命令,后面的repo branch要和相匹配的git repo branch相对应。
使用命令:
du -h --max-depth=1
看一下这个工程的大小:
user@host037-ubuntu-1804:~/src$ du -h --max-depth=1
677M ./.repo
76M ./art
44M ./bionic
15M ./bootable
15M ./build
1.4G ./cts
26M ./dalvik
405M ./developers
132M ./development
1.8G ./device
7.7G ./external
1.9G ./frameworks
185M ./hardware
1.2M ./kernel
84M ./libcore
372K ./libnativehelper
773M ./packages
752K ./pdk
4.5M ./platform_testing
32G ./prebuilts
27M ./sdk
500M ./system
398M ./test
102M ./toolchain
1.7G ./tools
50G .
一些版本控制的信息在.repo目录下。
需要重点关注下,.repo/目录下manifest.xml文档,它记录了整个项目的构成什么的。
这个命令提供了一个全局的git库的概述,主要包括各个分支在某个仓库的情况,如下:
vimer@host:~/src/aosp_art/art$ repo info
Manifest branch: zhimo-aosp
Manifest merge branch: refs/heads/zhimo-aosp-d_art
Manifest groups: all,-notdefault
----------------------------
Project: platform/art
Mount path: /home/vimer/src/aosp_art/art
Current revision: 9e22b01ffc829a3cdf9e66e07d6afd30eb1e3a7e
Local Branches: 5 [vimer_dev, run_test_yuzhen, run_test_0817, assembler_dev, fmv-s_dev]
以art为例,与art有关的代码是: 1.art目录 2. libcore目录,包含 jdk相关源代码 3.libnativehelper目录,包含JNI相关代码
frameworks/base/cmds/am、frameworks/base/core、frameworks/base/include目录,包含Zygote相关的源代码。这个函数还是挺特殊的,特意记录下。先看代码:
int main()
{
char str[80] = "This is my website";
const char s[2] = " ";
char* token;
token = strtok(str, s);
while(token != NULL){
printf("%s\n", token);
token = strtok(NULL, s);
}
return 0;
}
这里有一个违反直觉的使用方法,在while循环中,居然在使用strtok时传递给NULL参数,
这不对啊,那样的话,指针指向什么地方了?原来在第一次分割后
根据分隔符已经产生了”this”,也就是token指向了”this”,但是,str的指针存在一个静态
变量中,后面使用strtok函数传递给的参数NULL,只是一个符号。
以上,就是使用strtok的基本规则。
从接触linux至今,至少见到了Makefile编译系统,但是,在不同的平台上,这个Makefile 还需要进一步改写,这严重制约了项目的代码效率(特指维护方面)。CMake的提出就是解决这个问题的。
首先编制一个简单的c程序,命名为add.cc.
#include <stdio.h>
#include <stdlib.h>
int add_summary(int a,int b)
{
return (a + b);
}
int main()
{
int x,y;
printf("Please input two numbers and be carefully overflow\n");
scanf("%d %d", &x, &y);
printf("The sum of two nums is %d\n", add_summary(x, y));
}
在相同目录下,然后编写一个CMakeLists文件。
swin:~/test/cmake$ cat CMakeLists.txt
# CMake不区分大小写
# CMake最低的版本号
cmake_minimum_required (VERSION 3.0)
# 项目信息,与包含工程的目录一直
project (cmake)
# 指定生成文件及源文件
add_executable (cmake add.cc)
$: ~/test/cmake$ ls
add.cc CMakeLists.txt
然后在本地目录cmake .
cmake .
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/yubo/test/cmake
会生成:
yubo@win:~/test/cmake$ ls
add.cc CMakeCache.txt CMakeFiles cmake_install.cmake CMakeLists.txt Makefile
借着Make:
win:~/test/cmake$ make
Scanning dependencies of target cmake
[ 50%] Building CXX object CMakeFiles/cmake.dir/add.cc.o
[100%] Linking CXX executable cmake
[100%] Built target cmake
直接生成了可执行文件cmake:
swin:~/test/cmake$ ls
add.cc CMakeCache.txt cmake_install.cmake Makefile
cmake CMakeFiles CMakeLists.txt
看到Makefile文件,你就可以接着使用make命令的其他选项了.
只需修改CMakeLists.txt即可。
# 第一个参数是可执行文件的名字,然后依次将工程所需的源文件添加
# 进去
add_executable(cmake, add.cc, sub.cc)
从工作量上来说,如果源文件很多的话,这个任务还是很艰巨的。更省事的方法是使用aux_source_directory命令,该命令会将指定目录下的所有源文件,以特定变量名保存结果。
# 查找当前目录下的所有源文件
# 并将结果保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 然后指定生成目标
add_executable(cmake ${DIR_SRCS})
假设一个项目由多个目录构成,这也是在项目中非常常见的。上面我们将加法的 程序分离出去,编译成一个静态库。
假设只需要某个库的头文件,也可以通过Cmake控制。
vimer@host:~/git/AviSynthPlus$ mkdir avisynth
vimer@host:~/git/AviSynthPlus$ cd avisynth/
vimer@host:~/git/AviSynthPlus/avisynth$ ls
vimer@host:~/git/AviSynthPlus/avisynth$ cmake ../ -DHEADERS_ONLY:bool=on
-- Install Only Headers: ON
-- Configuring done
-- Generating done
-- Build files have been written to: /home/vimer/git/AviSynthPlus/avisynth
vimer@host:~/git/AviSynthPlus/avisynth$ make install
通过-DHEADERS_ONLY可以控制这个行为。